位置: IT常识 - 正文

SpringSecurity入门(springsecurity有什么用)

编辑:rootadmin
1、引入依赖 spring-boot版本2.7.3,如未特殊说明版本默认使用此版本 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactI ... 1、引入依赖spring-boot版本2.7.3,如未特殊说明版本默认使用此版本 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>2、编写controller并启动springboot服务@RestControllerpublic class HelloController { @GetMapping("/") public String hello(){ return "hello SpringSecurity"; }}启动

推荐整理分享SpringSecurity入门(springsecurity有什么用),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:springsecurity入门案例,springsecurity原理和机制,spring security入门,springsecurity入门案例,springsecurity入门案例,springsecurity入门案例,springsecurity入门案例,springsecurity入门案例,内容如对您有帮助,希望把文章链接给更多的朋友!

访问http://localhost:8080/

登陆使用账号:user,密码:04e74f23-0e97-4ee9-957e-2004a2e60692

SecurityProperties

3、自动配置SpringBootWebSecurityConfigurationSecurityFilterChainConfiguration

WebSecurityConfigurerAdapter中有所有的Security相关的配置,只需要继承重新对应属性即可完成自定义由于新版本的Security已经弃用WebSecurityConfigurerAdapter所以注册SecurityFilterChain即可 @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity.authorizeRequests() .mvcMatchers("/index").permitAll() .anyRequest().authenticated() .and() .formLogin() .and().build(); }

4、默认登陆页面DefaultLoginPageGeneratingFilter

4.1、SecurityFilterChainConfiguration默认实现的SecurityFilterChain容器中没有WebSecurityConfigurerAdapter类型的bean实例自动配置才会生效

4.2、UsernamePasswordAuthenticationFilter

4.3、attemptAuthentication方法

4.4、 ProviderManager的authenticate方法

4.5、 AuthenticationProvider实现AbstractUserDetailsAuthenticationProvider中的authenticate方法

4.6、 UserDetails实现类DaoAuthenticationProvider的retrieveUser方法

4.7、UserDetailsService实现类InMemoryUserDetailsManager的loadUserByUsername方法

4.8、 UserDetailsService

4.9、 UserDetailsServiceAutoConfiguration

容器中没有:AuthenticationManager、AuthenticationProvider、UserDetailsService、AuthenticationManagerResolver这4个bean实例才会加载InMemoryUserDetailsManager

4.10、 SecurityProperties

可以通过spring.security.user.password=123456自定义密码5、 自定义认证5.1、由于新版本的Security已经弃用WebSecurityConfigurerAdapter所以注册SecurityFilterChain即可

github示例

@Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity.authorizeRequests() .mvcMatchers("/index").permitAll() .anyRequest().authenticated() .and() .formLogin() .and().build(); }5.2、 自定义登陆页面5.2.1、html使用UsernamePasswordAuthenticationFilter用户名和密码字段名必须是username和password,且必须是POST的方式提交

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><head> <meta charset="UTF-8"> <title>login</title></head><body><form th:action="@{/doLogin}" method="post"> <p>用户名:<label> <input name="username" type="text"/> </label></p> <p>密码:<label> <input name="password" type="password"/> </label></p> <p> <input type="submit"> </p></form></body></html>5.2.2、SecurityFilterChain配置@Configurationpublic class WebSecurityConfigurer { @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin") .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }5.2.3、 controller@Controllerpublic class LoginController { @RequestMapping("toLogin") public String toLogin(){ return "login"; }}5.2.4、 自定义登陆使用的用户名和密码字段名使用usernameParameter和passwordParameter@Configurationpublic class WebSecurityConfigurer { @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd") .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}form表单中对应参数名也需要修改,用户名为:uname,密码为:pwd

5.3、 自定义认证成功后访问的页面successForwardUrl(转发),必须使用POST请求,每次都会跳转到指定请求defaultSuccessUrl(重定向),必须使用GET请求,不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使用.defaultSuccessUrl("/test",true)即可

二选一// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(重定向),必须使用GET请求 .defaultSuccessUrl("/test",true)5.4、 前后端分离处理方式

5.4.1、 实现AuthenticationSuccessHandler接口public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("msg", "登陆成功"); map.put("code", HttpStatus.OK); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); }}5.4.2、修改SecurityFilterChain配置使用successHandler(new MyAuthenticationSuccessHandler())@Configurationpublic class WebSecurityConfigurer { @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler()) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}5.4.3、返回数据

6、 认证失败处理failureForwardUrl,转发,请求必须是POSTfailureUrl,重定向,请求必须是GET6.1、org.springframework.security.authentication.ProviderManager#authenticate

6.2、 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)

6.3、 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#unsuccessfulAuthenticationSpringSecurity入门(springsecurity有什么用)

6.4、 org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#onAuthenticationFailure

6.5、 org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#saveException

如果是转发异常信息存在request里面如果是重定向异常信息存在session里面,默认是重定向参数名:SPRING_SECURITY_LAST_EXCEPTION6.7、 前端取值展示修改SecurityFilterChain配置@Configurationpublic class WebSecurityConfigurer { @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler())// 认证失败跳转页面,必须使用POST请求 .failureForwardUrl("/toLogin") // 认证失败跳转页面,,必须使用GET请求 // .failureUrl("/toLogin") .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}html增加取值<!-- 重定向错误信息存在session中 --><p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}"></p><!-- 转发错误信息存在request中 --><p th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></p>6.8、 前后端分离处理方式

6.8.1、 实现AuthenticationFailureHandler接口public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("msg", exception.getMessage()); map.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value()); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); }}6.8.2、修改SecurityFilterChain配置failureHandler@Configurationpublic class WebSecurityConfigurer { @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler())// 认证失败跳转页面,必须使用POST请求// .failureForwardUrl("/toLogin")// 认证失败跳转页面,必须使用GET请求// .failureUrl("/toLogin")// 前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}7、 注销登录7.1、 默认方式

.logout()// 指定注销url,默认请求方式GET.logoutUrl("/logout")// 注销成功后跳转页面.logoutSuccessUrl("/toLogin")

@Configurationpublic class WebSecurityConfigurer { @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler())// 认证失败跳转页面,必须使用POST请求// .failureForwardUrl("/toLogin")// 认证失败跳转页面,必须使用GET请求// .failureUrl("/toLogin")// 前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) .and()// 注销 .logout()// 指定注销url,默认请求方式GET .logoutUrl("/logout")// 销毁session,默认为true .invalidateHttpSession(true)// 清除认证信息,默认为true .clearAuthentication(true)// 注销成功后跳转页面 .logoutSuccessUrl("/toLogin") .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}7.2、 自定义方式

// 注销.logout()// 自定义注销url.logoutRequestMatcher(newOrRequestMatcher(newAntPathRequestMatcher("/aa","GET"),newAntPathRequestMatcher("/bb","POST")))// 注销成功后跳转页面.logoutSuccessUrl("/toLogin")

@Configurationpublic class WebSecurityConfigurer { @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler())// 认证失败跳转页面,必须使用POST请求// .failureForwardUrl("/toLogin")// 认证失败跳转页面,必须使用GET请求// .failureUrl("/toLogin")// 前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) .and()// 注销 .logout()// 指定注销url,默认请求方式GET// .logoutUrl("/logout") .logoutRequestMatcher(new OrRequestMatcher( new AntPathRequestMatcher("/aa","GET"), new AntPathRequestMatcher("/bb","POST") ))// 销毁session,默认为true .invalidateHttpSession(true)// 清除认证信息,默认为true .clearAuthentication(true)// 注销成功后跳转页面 .logoutSuccessUrl("/toLogin") .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}7.3、 前后端分离

7.3.1、 实现LogoutSuccessHandler接口public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String,Object> map = new HashMap<>(); map.put("msg", "注销成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); }}7.3.2、 修改SecurityFilterChain配置logoutSuccessHandler@Configurationpublic class WebSecurityConfigurer { @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler())// 认证失败跳转页面,必须使用POST请求// .failureForwardUrl("/toLogin")// 认证失败跳转页面,必须使用GET请求// .failureUrl("/toLogin")// 前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) .and()// 注销 .logout()// 指定默认注销url,默认请求方式GET// .logoutUrl("/logout")// 自定义注销url .logoutRequestMatcher(new OrRequestMatcher( new AntPathRequestMatcher("/aa","GET"), new AntPathRequestMatcher("/bb","POST") ))// 销毁session,默认为true .invalidateHttpSession(true)// 清除认证信息,默认为true .clearAuthentication(true)// 注销成功后跳转页面// .logoutSuccessUrl("/toLogin") .logoutSuccessHandler(new MyLogoutSuccessHandler()) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}8、获取用户认证信息

三种策略模式,调整通过修改VM options// 如果没有设置自定义的策略,就采用MODE_THREADLOCAL模式public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";// 采用InheritableThreadLocal,它是ThreadLocal的一个子类,适用多线程的环境public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";// 全局策略,实现方式就是static SecurityContext contextHolderpublic static final String MODE_GLOBAL = "MODE_GLOBAL";-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

8.1、 使用代码获取Authentication authentication = SecurityContextHolder.getContext().getAuthentication();User user = (User) authentication.getPrincipal();System.out.println("身份信息:" + authentication.getPrincipal());System.out.println("用户:" + user.getUsername());System.out.println("权限信息:" + authentication.getAuthorities());

8.2、 前端页面获取8.2.1、 引入依赖不需要版本号 <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>8.2.2、 导入命名空间,获取数据<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><head> <meta charset="UTF-8"> <title>login</title></head><body><form th:action="@{/bb}" method="post"> <p> <input type="submit" value="注销登陆"> </p></form><hr><h2>获取认证用户信息</h2><ul><!-- <li sec:authentication="name"></li>--><!-- <li sec:authentication="authorities"></li>--><!-- <li sec:authentication="credentials"></li>--><!-- <li sec:authentication="authenticated"></li>--> <li sec:authentication="principal.username"></li> <li sec:authentication="principal.authorities"></li> <li sec:authentication="principal.accountNonExpired"></li> <li sec:authentication="principal.accountNonLocked"></li> <li sec:authentication="principal.credentialsNonExpired"></li></ul></body></html>

9、 自定义数据源9.1、 流程分析

When the user submits their credentials, the AbstractAuthenticationProcessingFilter creates an Authentication from the HttpServletRequest to be authenticated. The type of Authentication created depends on the subclass of AbstractAuthenticationProcessingFilter. For example, UsernamePasswordAuthenticationFilter creates a UsernamePasswordAuthenticationToken from a username and password that are submitted in the HttpServletRequest. Next, the Authentication is passed into the AuthenticationManager to be authenticated. If authentication fails, then Failure

The SecurityContextHolder is cleared out.RememberMeServices.loginFail is invoked. If remember me is not configured, this is a no-op.AuthenticationFailureHandler is invoked.

If authentication is successful, then Success.

SessionAuthenticationStrategy is notified of a new log in.The Authentication is set on the SecurityContextHolder. Later the SecurityContextPersistenceFilter saves the SecurityContext to the HttpSession.RememberMeServices.loginSuccess is invoked. If remember me is not configured, this is a no-op.ApplicationEventPublisher publishes an InteractiveAuthenticationSuccessEvent.AuthenticationSuccessHandler is invoked.

9.2、 修改WebSecurityConfigurer @Autowired DataSource dataSource; @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } public UserDetailsService users(PasswordEncoder encoder) { // The builder will ensure the passwords are encoded before saving in memory UserDetails user = User.withUsername("user") .password(encoder.encode("123")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } public UserDetailsService users() { JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);// UserDetails admin = User.withUsername("齐丰")// .password(encoder.encode("123456"))// .roles("USER")// .build();// users.createUser(admin); System.out.println(dataSource.getClass()); return users; }9.3、 In-Memory Authentication修改SecurityFilterChain通过指定userDetailsService来切换不同的认证数据储存方式 @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin")// 自定义接收用户名的参数名为uname .usernameParameter("uname")// 自定义接收密码的参数名为pwd .passwordParameter("pwd")// 登陆认证成功后跳转的页面(转发),必须使用POST请求// .successForwardUrl("/test")// 陆认证成功后跳转的页面(转发),必须使用GET请求// .defaultSuccessUrl("/test",true) //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)// .defaultSuccessUrl("/test")// 前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler())// 认证失败跳转页面,必须使用POST请求// .failureForwardUrl("/toLogin")// 认证失败跳转页面,必须使用GET请求// .failureUrl("/toLogin")// 前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) .and()// 注销 .logout()// 指定默认注销url,默认请求方式GET// .logoutUrl("/logout")// 自定义注销url .logoutRequestMatcher(new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST") ))// 销毁session,默认为true .invalidateHttpSession(true)// 清除认证信息,默认为true .clearAuthentication(true)// 注销成功后跳转页面// .logoutSuccessUrl("/toLogin") .logoutSuccessHandler(new MyLogoutSuccessHandler()) .and() .userDetailsService(users(bcryptPasswordEncoder())) // .userDetailsService(users()) //禁止csrf跨站请求保护 .csrf().disable() .build(); }9.4、 JDBC Authentication9.3.1、 引入依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency>9.3.2、 数据库表及连接地址配置表结构官方文档org/springframework/security/core/userdetails/jdbc/users.ddl

数据库连连配置spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.0.0.128:3306/test username: wq password: 1234569.3.3、 修改SecurityFilterChain通过指定userDetailsService来切换不同的认证数据储存方式@Configuration public class WebSecurityConfigurer { @Autowired DataSource dataSource; @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } public UserDetailsService inMemoryUsers(PasswordEncoder encoder) { // The builder will ensure the passwords are encoded before saving in memory UserDetails user = User.withUsername("user") .password(encoder.encode("123")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } public UserDetailsService jdbcUsers(PasswordEncoder encoder) { JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource); // UserDetails admin = User.withUsername("齐丰") // .password(encoder.encode("123456")) // .roles("USER") // .build(); // users.deleteUser(admin.getUsername()); // users.createUser(admin); System.out.println(dataSource.getClass()); return users; } @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin(login -> login //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin") // 自定义接收用户名的参数名为uname .usernameParameter("uname") //自定义接收密码的参数名为pwd .passwordParameter("pwd") // 认证成功后跳转的页面(转发),必须使用POST请求 //.successForwardUrl("/test") // 证成功后跳转的页面(重定向),必须使用GET请求 //.defaultSuccessUrl("/test") //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true) //.defaultSuccessUrl("/test",true) //前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler()) //前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) ) //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET //.logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //注销成功后跳转页面 //.logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) .userDetailsService(jdbcUsers(bcryptPasswordEncoder())) //禁止csrf跨站请求保护 .csrf().disable() .build(); } }方式一:通过指定userDetailsService来切换不同的认证数据储存方式,也可同时指定多个如:.userDetailsService(users(bcryptPasswordEncoder())).userDetailsService(users())方式二:在指定自定义的UserDetailsService上加上@Bean注解,或者实现UserDetailsService接口

9.5、扩展JDBC Authentication之mysql表设计

-- test1.`user` definitionCREATE TABLE `user` ( `userId` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(100) NOT NULL COMMENT '用户名', `password` varchar(500) NOT NULL COMMENT '密码', `accountNonExpired` tinyint(1) NOT NULL, `enabled` tinyint(1) NOT NULL, `accountNonLocked` tinyint(1) NOT NULL, `credentialsNonExpired` tinyint(1) NOT NULL, PRIMARY KEY (`userId`), UNIQUE KEY `user_UN` (`username`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- test1.`role` definitionCREATE TABLE `role` ( `roleId` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `name_zh` varchar(100) NOT NULL, PRIMARY KEY (`roleId`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- test1.`role` definitionCREATE TABLE `role` ( `roleId` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `name_zh` varchar(100) NOT NULL, PRIMARY KEY (`roleId`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;INSERT INTO test1.`user` (username,password,accountNonExpired,enabled,accountNonLocked,credentialsNonExpired) VALUES ('root','{noop}123',1,1,1,1), ('admin','{noop}123',1,1,1,1), ('qifeng','{noop}123',1,1,1,1);INSERT INTO test1.`role` (name,name_zh) VALUES ('ROLE_product','商品管理员'), ('ROLE_admin','系统管理员'), ('ROLE_user','用户管理员');INSERT INTO test1.user_role (userId,roleId) VALUES (1,1), (1,2), (2,2), (3,3);9.5.1、引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>9.5.2、配置数据库连接server: port: 8081spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.0.0.128:3306/test1 username: wq password: 123456mybatis: type-aliases-package: com.wanqi.pojo mapper-locations: classpath:mapper/*.xml9.5.3、编写实体类与MapperUserUser继承UserDetails方便扩展public class User implements UserDetails { private Long userId; private String username; private String password; /** 账户是否过期 */ private Boolean accountNonExpired; /** 账户是否激活 */ private Boolean enabled; /** 账户是否被锁定 */ private Boolean accountNonLocked; /** 密码是否过期 */ private Boolean credentialsNonExpired; private List<Role> roles = new ArrayList<>(); public User() { } public User(String username, String password, Boolean accountNonExpired, Boolean enabled, Boolean accountNonLocked, Boolean credentialsNonExpired, List<Role> roles) { this.username = username; this.password = password; this.accountNonExpired = accountNonExpired; this.enabled = enabled; this.accountNonLocked = accountNonLocked; this.credentialsNonExpired = credentialsNonExpired; this.roles = roles; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Set<SimpleGrantedAuthority> authorities = new HashSet<>(); roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName()))); return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return accountNonExpired; } @Override public boolean isAccountNonLocked() { return accountNonLocked; } @Override public boolean isCredentialsNonExpired() { return credentialsNonExpired; } @Override public boolean isEnabled() { return enabled; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAccountNonExpired(Boolean accountNonExpired) { this.accountNonExpired = accountNonExpired; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public void setAccountNonLocked(Boolean accountNonLocked) { this.accountNonLocked = accountNonLocked; } public void setCredentialsNonExpired(Boolean credentialsNonExpired) { this.credentialsNonExpired = credentialsNonExpired; }}@Mapperpublic interface UserMapper { /** * 根据用户名查询用户 */ User loadUserByUsername(@Param("username") String username);}<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wanqi.mapper.UserMapper"><select id="loadUserByUsername" parameterType="String" resultType="user"> select * from user where username=#{username}</select></mapper>Rolepublic class Role { private Long roleId; private String name; private String name_zh; public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getName_zh() { return name_zh; } public void setName_zh(String name_zh) { this.name_zh = name_zh; } public Role() { } public Role(String name, String name_zh) { this.name = name; this.name_zh = name_zh; }}@Mapperpublic interface RoleMapper { /** * 根据用户编号查询角色信息 */ List<Role> getRoleByUserId(@Param("userId") Long userId);}<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wanqi.mapper.RoleMapper"><select id="getRoleByUserId" parameterType="Long" resultType="com.wanqi.pojo.Role"> select r.roleId, r.name, r.name_zh from role r,user_role ur where r.roleId = ur.roleId and ur.userId= #{userId}</select></mapper>9.5.4、实现UserDetailsService接口@Component("userDetailsImpl")public class UserDetailsImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private RoleMapper roleMapper; @Override public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.loadUserByUsername(username); if(user == null){ throw new UsernameNotFoundException("用户名不存在"); } List<Role> roles = roleMapper.getRoleByUserId(user.getUserId()); user.setRoles(roles); return user; }}9.5.5、指定认证数据源通过 httpSecurity.userDetailsService(userDetailsImpl)@Configuration public class MyWebSecurityConfigurer { private UserDetailsImpl userDetailsImpl; @Autowired public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) { this.userDetailsImpl = userDetailsImpl; } @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin(login -> login //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin") // 自定义接收用户名的参数名为uname .usernameParameter("uname") //自定义接收密码的参数名为pwd .passwordParameter("pwd") // 认证成功后跳转的页面(转发),必须使用POST请求 //.successForwardUrl("/test") // 证成功后跳转的页面(重定向),必须使用GET请求 //.defaultSuccessUrl("/test") //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true) //.defaultSuccessUrl("/test",true) //前后端分离时代自定义认证成功处理 .successHandler(new MyAuthenticationSuccessHandler()) //前后端分离时代自定义认证失败处理 .failureHandler(new MyAuthenticationFailureHandler()) ) //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET //.logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //注销成功后跳转页面 //.logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(userDetailsImpl) //禁止csrf跨站请求保护 .csrf().disable() .build(); } }10、自定义JSON认证,前后端分离10.1、HttpSecurity过滤器方法介绍/** at:用指定的filter替换过滤器链中的指定filter* before:放在过滤器链中哪一个filter之前* after:放在过滤器链中哪一个filter之后* httpSecurity.addFilterAt();* httpSecurity.addFilterBefore(, );* httpSecurity.addFilterAfter();* */10.2、自定义登录处理filterpublic class JsonLoginFilter extends UsernamePasswordAuthenticationFilter { public JsonLoginFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { System.out.println("JosnLoginFilter"); if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) { try { Map<String,String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class); String username = map.get(getUsernameParameter()); username = (username != null) ? username.trim() : ""; String password = map.get(getPasswordParameter()); password = (password != null) ? password : ""; UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); System.out.println(username); System.out.println(password); setDetails(request, authRequest); return getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { e.printStackTrace(); } } return super.attemptAuthentication(request, response); }}10.3、配置文件编写@Configuration@EnableWebSecuritypublic class WebSecurityConfigurer { private UserDetailsImpl userDetailsImpl; @Autowired public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) { this.userDetailsImpl = userDetailsImpl; } @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Autowired AuthenticationConfiguration authenticationConfiguration; /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager() throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public JsonLoginFilter josnLoginFilter() throws Exception { System.out.println("setAuthenticationManager"); JsonLoginFilter filter = new JsonLoginFilter(authenticationManager()); filter.setUsernameParameter("uname"); filter.setPasswordParameter("pwd"); //前后端分离时代自定义认证成功处理 filter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler()); //前后端分离时代自定义认证失败处理 filter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler()); return filter; } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() .and() .addFilterAt(josnLoginFilter(),UsernamePasswordAuthenticationFilter.class) //注销 .logout(logout -> { logout .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(userDetailsImpl) .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> {// response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); response.setHeader("content-type", "application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("请先认证后重试!"); }) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}10.4、使用自定义filter注意事项httpSecurity.formLogin()必须使用无参的11、实现kaptcha图片验证码引入依赖<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version></dependency>1、编写kaptcha配置类@Configurationpublic class KaptchaConfig { @Bean public Producer kaptchaProducer() { Properties properties = new Properties(); //图片的宽度 properties.setProperty("kaptcha.image.width", "150"); //图片的高度 properties.setProperty("kaptcha.image.height", "50"); //字体大小 properties.setProperty("kaptcha.textproducer.font.size", "32"); //字体颜色(RGB) properties.setProperty("kaptcha.textproducer.font.color", "0,0,0"); //验证码字符的集合 properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); //验证码长度(即在上面集合中随机选取几位作为验证码) properties.setProperty("kaptcha.textproducer.char.length", "4"); //图片的干扰样式 properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise"); DefaultKaptcha Kaptcha = new DefaultKaptcha(); Config config = new Config(properties); Kaptcha.setConfig(config); return Kaptcha; }}2、自定义异常public class KaptchaNotMatchException extends AuthenticationException { public KaptchaNotMatchException(String msg, Throwable cause) { super(msg, cause); } public KaptchaNotMatchException(String msg) { super(msg); }}11.1、传统web开发方式3、自定义Filterpublic class VerifyCodeFilter extends UsernamePasswordAuthenticationFilter { public static final String VERIFY_CODE_KEY = "kaptcha"; private String kaptchaParameter = VERIFY_CODE_KEY; public void setKaptchaParameter(String kaptchaParameter) { this.kaptchaParameter = kaptchaParameter; } public String getKaptchaParameter() { return kaptchaParameter; } public VerifyCodeFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String verifyCode = request.getParameter(getKaptchaParameter()); String verifyCodeOld = (String) request.getSession().getAttribute("verifyCode"); if (StringUtils.hasLength(verifyCode) && StringUtils.hasLength(verifyCodeOld) && verifyCode.equalsIgnoreCase(verifyCodeOld)) { return super.attemptAuthentication(request, response); } throw new KaptchaNotMatchException("验证码错误"); }}4、提供生成验证码的接口private Producer producer;@Autowired public void setProducer(Producer producer) { this.producer = producer;}@RequestMapping("/vc.img") public void img(HttpSession session, HttpServletResponse response) throws IOException { String verifyCode = producer.createText(); BufferedImage image = producer.createImage(verifyCode); session.setAttribute("verifyCode",verifyCode); ServletOutputStream outputStream = response.getOutputStream(); response.setContentType(MediaType.IMAGE_JPEG_VALUE); ImageIO.write(image, "jpg", outputStream);}5、登录页面添加验证码<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><head> <meta charset="UTF-8"> <title>login</title></head><body><p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}"></p><form th:action="@{/doLogin}" method="post"> <p>用户名:<label> <input name="uname" type="text"/> </label></p> <p>密码:<label> <input name="pwd" type="password"/> </label></p> <p>验证码:<label> <input name="yzm" type="text"/> <img th:src="https://www.cnblogs.com/wandaren/archive/2022/10/29/@{/vc.img}" alt=""/> </label></p> <p> <input type="submit"> </p> <p th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></p></form></body></html>6、配置WebSecurityConfigurer@Configuration @EnableWebSecurity public class MyWebSecurityConfigurer { private UserDetailsImpl userDetailsImpl; @Autowired public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) { this.userDetailsImpl = userDetailsImpl; } @Autowired AuthenticationConfiguration authenticationConfiguration; @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager() throws Exception { return authenticationConfiguration.getAuthenticationManager(); } public VerifyCodeFilter verifyCodeFilter() throws Exception { VerifyCodeFilter filter = new VerifyCodeFilter(authenticationManager()); // 自定义接收用户名的参数名为uname filter.setUsernameParameter("uname"); //自定义接收密码的参数名为pwd filter.setPasswordParameter("pwd"); filter.setKaptchaParameter("yzm"); filter.setFilterProcessesUrl("/doLogin"); //前后端分离时代自定义认证成功处理 filter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler()); //前后端分离时代自定义认证失败处理 filter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler()); return filter; } @Bean @SuppressWarnings("all") SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/vc.img").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() .loginPage("/toLogin") .and() .addFilterAt(verifyCodeFilter(), UsernamePasswordAuthenticationFilter.class) //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET //.logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //注销成功后跳转页面 .logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器 // .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true) ;}) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(userDetailsImpl) //禁止csrf跨站请求保护 .csrf().disable() .build(); } }11.2、前后端分离方式3、提供生成验证码的接口private Producer producer;@Autowired public void setProducer(Producer producer) { this.producer = producer;}@RequestMapping("/cv.img") public String verifyCoder(HttpSession session, HttpServletResponse response) throws IOException { String verifyCode = producer.createText(); BufferedImage image = producer.createImage(verifyCode); session.setAttribute("verifyCode",verifyCode); FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream(); ImageIO.write(image, "jpg", outputStream); return Base64Utils.encodeToString(outputStream.toByteArray());}4、自定义Filterpublic class JsonLoginFilter extends UsernamePasswordAuthenticationFilter { public static final String VERIFY_CODE_KEY = "kaptcha"; private String kaptchaParameter = VERIFY_CODE_KEY; public void setKaptchaParameter(String kaptchaParameter) { this.kaptchaParameter = kaptchaParameter; } public String getKaptchaParameter() { return kaptchaParameter; } public JsonLoginFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) { try { Map<String, String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class); String verifyCode = map.get(getKaptchaParameter()); String verifyCodeOld = (String) request.getSession().getAttribute("verifyCode"); if (!ObjectUtils.isEmpty(verifyCode) && !ObjectUtils.isEmpty(verifyCodeOld) && verifyCode.equalsIgnoreCase(verifyCodeOld)) { String username = map.get(getUsernameParameter()); username = (username != null) ? username.trim() : ""; String password = map.get(getPasswordParameter()); password = (password != null) ? password : ""; UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); System.out.println(username); System.out.println(password); setDetails(request, authRequest); return getAuthenticationManager().authenticate(authRequest); } } catch (IOException e) { e.printStackTrace(); } } throw new KaptchaNotMatchException("验证码错误"); }}5、配置WebSecurityConfigurer@Configuration@EnableWebSecuritypublic class WebSecurityConfigurer { private UserDetailsImpl userDetailsImpl; @Autowired public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) { this.userDetailsImpl = userDetailsImpl; } @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Autowired AuthenticationConfiguration authenticationConfiguration; /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager() throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public JsonLoginFilter jsonLoginFilter() throws Exception { JsonLoginFilter filter = new JsonLoginFilter(authenticationManager()); filter.setUsernameParameter("uname"); filter.setPasswordParameter("pwd"); filter.setKaptchaParameter("yzm"); //前后端分离时代自定义认证成功处理 filter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler()); //前后端分离时代自定义认证失败处理 filter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler()); return filter; } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/cv.img").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin() .and() .addFilterAt(jsonLoginFilter(),UsernamePasswordAuthenticationFilter.class) //注销 .logout(logout -> { logout .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(userDetailsImpl) .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { //response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); response.setHeader("content-type", "application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("请先认证后重试!"); }) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}12、密码加密12.1、不指定具体加密方式,通过DelegatingPasswordEncoder,根据前缀自动选择PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

12.2、指定具体加密方式// Create an encoder with strength 16BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));// Create an encoder with all the defaultsArgon2PasswordEncoder encoder = new Argon2PasswordEncoder();String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));// Create an encoder with all the defaultsPbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));// Create an encoder with all the defaultsSCryptPasswordEncoder encoder = new SCryptPasswordEncoder();String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));13、密码自动更新

实现UserDetailsPasswordService接口即可

@Component("userDetailsImpl")public class UserDetailsImpl implements UserDetailsService, UserDetailsPasswordService { @Autowired private UserMapper userMapper; @Autowired private RoleMapper roleMapper; @Override public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.loadUserByUsername(username); if(user == null){ throw new UsernameNotFoundException("用户名不存在"); } List<Role> roles = roleMapper.getRoleByUserId(user.getUserId()); user.setRoles(roles); return user; } @Override public UserDetails updatePassword(UserDetails user, String newPassword) { int state = userMapper.updatePassword(newPassword, user.getUsername()); if (state == 1) { ((User) user).setPassword(newPassword); } return user; }}14、Remember-Me14.1、传统web方式14.1.1、login.html<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><head> <meta charset="UTF-8"> <title>login</title></head><body><form th:action="@{/doLogin}" method="post"> <p>用户名:<label> <input name="uname" type="text"/> </label></p> <p>密码:<label> <input name="pwd" type="password"/> </label></p> <p>记住我:<label> <input name="remember-me" type="checkbox"/> </label></p> <p> <input type="submit"> </p></form></body></html>14.1.2、开启rememberMeServices基于内存实现package com.wanqi.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import java.util.UUID;@Configuration@EnableWebSecuritypublic class WebSecurityConfigurer { @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } public UserDetailsService inMemoryUsers(PasswordEncoder encoder) { // The builder will ensure the passwords are encoded before saving in memory UserDetails user = User.withUsername("user") .password(encoder.encode("123")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean(name = "rememberMeServices") public PersistentTokenBasedRememberMeServices rememberMeServices(){ return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), inMemoryUsers(bcryptPasswordEncoder()), new InMemoryTokenRepositoryImpl()); } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin(login -> login //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin") // 自定义接收用户名的参数名为uname .usernameParameter("uname") //自定义接收密码的参数名为pwd .passwordParameter("pwd") // 认证成功后跳转的页面(转发),必须使用POST请求 //.successForwardUrl("/test") // 证成功后跳转的页面(重定向),必须使用GET请求 //.defaultSuccessUrl("/test") //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true) .defaultSuccessUrl("/toLogout",true) //前后端分离时代自定义认证成功处理// .successHandler(new MyAuthenticationSuccessHandler()) //前后端分离时代自定义认证失败处理// .failureHandler(new MyAuthenticationFailureHandler()) .failureUrl("/404") ) //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET// .logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //注销成功后跳转页面 .logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器// .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) //开启rememberMe .rememberMe() //自定义rememberMe参数名// .rememberMeParameter(); .rememberMeServices(rememberMeServices()) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}持久化,基于mysql实现 @Bean public PersistentTokenRepository jdbcTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //自动创建表结构,首次启动设置为true// jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return //开启权限验证 httpSecurity .authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/404").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin(login -> login //自定义登陆页面 .loginPage("/toLogin") //自定义登陆页面后必须指定处理登陆请求的url .loginProcessingUrl("/doLogin") // 自定义接收用户名的参数名为uname .usernameParameter("uname") //自定义接收密码的参数名为pwd .passwordParameter("pwd") // 认证成功后跳转的页面(转发),必须使用POST请求 //.successForwardUrl("/test") // 证成功后跳转的页面(重定向),必须使用GET请求 //.defaultSuccessUrl("/test") //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true) .defaultSuccessUrl("/toLogout",true) //前后端分离时代自定义认证成功处理// .successHandler(new MyAuthenticationSuccessHandler()) //前后端分离时代自定义认证失败处理// .failureHandler(new MyAuthenticationFailureHandler()) .failureUrl("/404") ) //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET// .logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //注销成功后跳转页面 .logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器// .logoutSuccessHandler(new MyLogoutSuccessHandler()) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) //开启rememberMe .rememberMe() .tokenRepository(jdbcTokenRepository()) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}14.2、前后端分离14.2.1、自定义RememberMeServices实现类package com.wanqi.service;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import javax.servlet.http.HttpServletRequest;/** * @Description 自定义RememberMeServices实现类 * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */public class RememberMeServices extends PersistentTokenBasedRememberMeServices { public RememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { super(key, userDetailsService, tokenRepository); } @Override protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { String paramValue = request.getAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER).toString(); return paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"); }}14.2.2、自定义认证方式package com.wanqi.security.filter;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.http.MediaType;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.AuthenticationServiceException;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;import org.springframework.util.ObjectUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Map;/** * @Description 处理Json方式的登录请求 * @Version 1.0.0 * @Date 2022/8/21 * @Author wandaren */public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter { public JsonLoginFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) { try { Map<String, String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class); String username = map.get(getUsernameParameter()); username = (username != null) ? username.trim() : ""; String password = map.get(getPasswordParameter()); password = (password != null) ? password : ""; String rememberMe = map.get(AbstractRememberMeServices.DEFAULT_PARAMETER); if (!ObjectUtils.isEmpty(rememberMe)) { request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER, rememberMe); } UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); System.out.println(username); System.out.println(password); setDetails(request, authRequest); return getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { e.printStackTrace(); } } throw new AuthenticationServiceException("Authentication ContentType not supported: " + request.getContentType()); }}14.2.3、配置filter.setRememberMeServices(rememberMeServices());.rememberMe().rememberMeServices(rememberMeServices()).tokenRepository(jdbcTokenRepository())package com.wanqi.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.wanqi.security.filter.JsonLoginFilter;import com.wanqi.service.RememberMeServices;import com.wanqi.service.impl.UserDetailsImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.core.Authentication;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.sql.DataSource;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.UUID;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig { @Autowired DataSource dataSource; UserDetailsImpl userDetailsImpl; @Autowired public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) { this.userDetailsImpl = userDetailsImpl; } @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Autowired public AuthenticationConfiguration authenticationConfiguration; /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager() throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public JsonLoginFilter jsonLoginFilter() throws Exception { JsonLoginFilter filter = new JsonLoginFilter(authenticationManager()); //指定认证url filter.setFilterProcessesUrl("/doLogin"); //指定接收json,用户名key filter.setUsernameParameter("uname"); //指定接收json,密码key filter.setPasswordParameter("pwd"); filter.setAuthenticationManager(authenticationManager()); filter.setRememberMeServices(rememberMeServices()); //前后端分离时代自定义认证成功处理 filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(); map.put("msg", "登陆成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); } }); //前后端分离时代自定义认证失败处理 filter.setAuthenticationFailureHandler((request, response, exception) -> { Map<String, Object> map = new HashMap<>(); map.put("msg", exception.getMessage()); map.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value()); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); }); return filter; } @Bean public PersistentTokenRepository jdbcTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //自动创建表结构,首次启动设置为true// jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Bean public RememberMeServices rememberMeServices(){ return new RememberMeServices(UUID.randomUUID().toString(), userDetailsImpl, jdbcTokenRepository()); } @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class) .logout(logout -> { logout .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(); map.put("msg", "注销成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); } }) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(userDetailsImpl) .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> {// response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); response.setHeader("content-type", "application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("请先认证后重试!"); }) .and() .rememberMe() .rememberMeServices(rememberMeServices()) .tokenRepository(jdbcTokenRepository()) .and() //禁止csrf跨站请求保护 .csrf().disable() .build(); }}15、会话管理

15.1、配置package com.wanqi.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.session.HttpSessionEventPublisher;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{ httpSecurity.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl("/hello",true) .and() .csrf().disable() .sessionManagement(session -> session //最多一个会话 .maximumSessions(1) //true:超过会话上限不再容许登录// .maxSessionsPreventsLogin(true) // 会话失效(用户被挤下线后)跳转地址 // .expiredUrl("/login") // 前后端分离处理方式 .expiredSessionStrategy(new SessionInformationExpiredStrategy() { @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { event.getResponse().setHeader("content-type", "application/json;charset=UTF-8"); event.getResponse().setStatus(HttpStatus.UNAUTHORIZED.value()); event.getResponse().getWriter().write("会话超时!"); } }) ); return httpSecurity.build(); } @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }}15.2、共享会话15.2.1、引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>15.2.2、编写配置application.ymlspring: redis: host: 172.16.156.139 port: 6379 password: qifeng database: 0 main: allow-circular-references: trueRedisSessionConfigpackage com.wanqi.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisStandaloneConfiguration;import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import org.springframework.security.web.session.HttpSessionEventPublisher;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */@Configuration@EnableRedisHttpSessionpublic class RedisSessionConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.database}") private int database; @Bean RedisStandaloneConfiguration redisStandaloneConfiguration() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(port); redisStandaloneConfiguration.setPassword(password); redisStandaloneConfiguration.setDatabase(database); return redisStandaloneConfiguration; } @Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(redisStandaloneConfiguration()); } @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }}SecurityConfigpackage com.wanqi.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.session.HttpSessionEventPublisher;import org.springframework.security.web.session.SessionInformationExpiredEvent;import org.springframework.security.web.session.SessionInformationExpiredStrategy;import org.springframework.session.FindByIndexNameSessionRepository;import org.springframework.session.Session;import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;import org.springframework.session.security.SpringSessionBackedSessionRegistry;import javax.servlet.ServletException;import java.io.IOException;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig<S extends Session> { @Autowired private FindByIndexNameSessionRepository<S> sessionRepository; @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Bean public UserDetailsService inMemoryUsers(PasswordEncoder encoder) { // The builder will ensure the passwords are encoded before saving in memory UserDetails user = User.withUsername("user") .password(encoder.encode("123")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{ httpSecurity.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl("/hello",true) .and() .csrf().disable() .sessionManagement(session -> session //最多一个会话 .maximumSessions(1) //true超过会话上限不再容许登录 .maxSessionsPreventsLogin(true)// 被踢下线后跳转地址// .expiredUrl("/login") .expiredSessionStrategy(new SessionInformationExpiredStrategy() { @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { event.getResponse().setHeader("content-type", "application/json;charset=UTF-8"); event.getResponse().setStatus(HttpStatus.UNAUTHORIZED.value()); event.getResponse().getWriter().write("会话超时!"); } }) .sessionRegistry(sessionRegistry()) ) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) ; return httpSecurity.build(); } @Bean public SpringSessionBackedSessionRegistry<S> sessionRegistry() { return new SpringSessionBackedSessionRegistry<>(this.sessionRepository); }}16、CSRF防御16.1、传统web开发登录页面加上_csrf <input type="hidden" name="_csrf" th:value="${_csrf.getToken()}">SecurityConfig配置修改httpSecurity.csrf()16.2、前后端分离开发16.2.1、登陆页面不需要令牌httpSecurity.csrf() //SpringSecurity处理登陆的默认方法不加令牌 .ignoringAntMatchers("/doLogin") //将令牌保存到cookie,容许前端获取 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())登陆后返回cookie中包含XSRF-TOKEN

后续请求在请求头增加X-XSRF-TOKEN,值为登陆cookie中的XSRF-TOKEN值

17、跨越处理17.1、spring跨越处理17.1.1、使用@CrossOrigin,可以作用到类上也可以作用到具体方法上@RestControllerpublic class HelloController { @RequestMapping("/hello") @CrossOrigin public String hello(){ return "hello"; }}17.1.2、实现WebMvcConfigurer接口重写addCorsMappings方法package com.wanqi.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/6 * @Author wandaren */@Configurationpublic class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { //对哪些请求进行跨域处理 registry.addMapping("/**") .allowCredentials(false) .allowedHeaders("*") .allowedMethods("*") .allowedOrigins("*") .exposedHeaders("") .maxAge(3600) ; }}17.1.3、使用CorsFilter @Bean FilterRegistrationBean<CorsFilter> corsFilter() { FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>(); CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); corsConfiguration.setAllowedHeaders(Collections.singletonList("*")); corsConfiguration.setAllowedMethods(Collections.singletonList("*")); corsConfiguration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); registrationBean.setFilter(new CorsFilter(source)); //指定Filter执行顺序 registrationBean.setOrder(-1); return registrationBean; }17.2、SpringSecurity跨域处理17.2.1、Security配置@Beanpublic CorsConfigurationSource configurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); corsConfiguration.setAllowedHeaders(Collections.singletonList("*")); corsConfiguration.setAllowedMethods(Collections.singletonList("*")); corsConfiguration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); return source;}httpSecurity.cors() .configurationSource(configurationSource())18、权限管理/授权18.1、针对url配置配置SecurityConfigpackage com.wanqi.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.CorsConfigurationSource;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import java.util.Collections;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig { @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Bean public UserDetailsService inMemoryUsers(PasswordEncoder encoder) { // The builder will ensure the passwords are encoded before saving in memory UserDetails user = User.withUsername("user") .password(encoder.encode("123")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("ADMIN") .build(); UserDetails qifeng = User.withUsername("qifeng") .password(encoder.encode("123")) .authorities("READ_HELLO","ROLE_ADMIN","ROLE_USER") .build(); return new InMemoryUserDetailsManager(user, admin, qifeng); } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() //权限READ_HELLO .mvcMatchers("/hello").hasAuthority("READ_HELLO") //角色 .mvcMatchers("/admin").hasRole("ADMIN") .mvcMatchers("/user").hasRole("USER") .anyRequest().authenticated() .and() .csrf().disable() .formLogin() .and() .logout(logout -> logout.logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"))) .logoutSuccessUrl("/login") ) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) ; return httpSecurity.build(); }}编码HelloControllerpackage com.wanqi.controller;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/6 * @Author wandaren */@RestControllerpublic class HelloController { @RequestMapping("/hello") public String hello(){ return "hello"; } @RequestMapping("/admin") public String admin(){ return "admin"; } @RequestMapping("/user") public String user(){ return "user"; }}18.2、针对方法配置基于注解EnableMethodSecurity@EnableMethodSecurity(prePostEnabled = true)

配置开启注解@Configuration@EnableWebSecurity@EnableMethodSecurity(prePostEnabled = true)public class SecurityConfig {编码测试接口package com.wanqi.controller;import com.wanqi.pojo.DataDamo;import org.springframework.security.access.prepost.PostAuthorize;import org.springframework.security.access.prepost.PostFilter;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.access.prepost.PreFilter;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/6 * @Author wandaren */@RestController@RequestMapping("/hi")public class MethodController { @PreAuthorize("hasRole('ADMIN') and authentication.name=='qifeng'") @RequestMapping public String hi() { return "hi"; } @PreAuthorize("authentication.name==#name") @RequestMapping("h1") public String hello(String name) { return "hello: " + name; } //filterTarget必须是:数组/集合 @PreFilter(value = "filterObject.id%2 != 0", filterTarget = "dataDamos") @RequestMapping("users") public String users(@RequestBody List<DataDamo> dataDamos) { return "hello: " + dataDamos; } @PostAuthorize("returnObject.id==1") @RequestMapping("userId") public DataDamo userId(Integer id) { return new DataDamo(id, "ssss"); } @PostFilter("filterObject.id>5") @RequestMapping("lists") public List<DataDamo> lists() { List<DataDamo> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new DataDamo(i, "sss" + i)); } return list; }}19、基于数据库权限验证19.1、表结构菜单/权限角色(可访问)/admin/**ROLE_ADMIN/user/**ROLE_USER/guest/**ROLE_GUEST用户角色adminADMIN、USERuserUSERqifengGUEST

19.2、sql菜单/权限CREATE TABLE `menu` ( `id` bigint NOT NULL AUTO_INCREMENT, `pattern` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO menu (pattern) VALUES ('/admin/**'), ('/user/**'), ('/guest/**');角色CREATE TABLE `role` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL, `name_zh` varchar(100) COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO `role` (name,name_zh) VALUES ('ROLE_ADMIN','管理员'), ('ROLE_USER','普通用户'), ('ROLE_GUEST','游客');菜单/权限-角色关系CREATE TABLE `menu_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `mid` bigint NOT NULL, `rid` bigint NOT NULL, PRIMARY KEY (`id`), KEY `menu_role_mid_IDX` (`mid`,`rid`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO `security`.menu_role (mid,rid) VALUES (1,1), (2,2), (3,2), (3,3);用户CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', `password` varchar(500) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', `accountNonExpired` tinyint(1) NOT NULL COMMENT '账户是否过期', `enabled` tinyint(1) NOT NULL COMMENT '账户是否激活', `accountNonLocked` tinyint(1) NOT NULL COMMENT '账户是否被锁定', `credentialsNonExpired` tinyint(1) NOT NULL COMMENT '密码是否过期', PRIMARY KEY (`id`), KEY `user_username_IDX` (`username`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO `user` (username,password,accountNonExpired,enabled,accountNonLocked,credentialsNonExpired) VALUES ('admin','{noop}123',1,1,1,1), ('user','{noop}123',1,1,1,1), ('qifeng','{noop}123',1,1,1,1);用户-角色关系CREATE TABLE `user_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `uid` bigint NOT NULL COMMENT '用户编号', `rid` bigint NOT NULL COMMENT '角色编号', PRIMARY KEY (`id`), KEY `user_role_userId_IDX` (`uid`,`rid`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO `security`.user_role (uid,rid) VALUES (1,1), (1,2), (2,2), (3,3);19.3、依赖,数据库配置<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>server: port: 8081spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://172.16.156.139:3306/security?allowPublicKeyRetrieval=true username: wq password: qifengmybatis: type-aliases-package: com.wanqi.pojo mapper-locations: classpath:mapper/*.xml19.4、实体类与mapper菜单package com.wanqi.pojo;import java.util.ArrayList;import java.util.List;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/7 * @Author wandaren */public class Menu { private Long id; private String pattern; private List<Role> roles = new ArrayList<>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public Menu(String pattern) { this.pattern = pattern; } public Menu() { } @Override public String toString() { return "Menu{" + "id=" + id + ", pattern='" + pattern + '\'' + ", roles=" + roles + '}'; }}package com.wanqi.mapper;import com.wanqi.pojo.Menu;import org.apache.ibatis.annotations.Mapper;import java.util.List;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/7 * @Author wandaren */@Mapperpublic interface MenuMapper { public List<Menu> getAllMenu();}<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wanqi.mapper.MenuMapper"> <resultMap id="MenuResultMap" type="com.wanqi.pojo.Menu"> <id property="id" column="id"/> <result property="pattern" column="pattern"/> <collection property="roles" ofType="com.wanqi.pojo.Role"> <id property="id" column="rid"/> <result property="name" column="rname"/> <result property="nameZh" column="rnameZh"/> </collection> </resultMap> <select id="getAllMenu" resultMap="MenuResultMap"> Select m.*, r.id as rid, r.name as rname, r.name_zh as rnameZh From menu m Left join menu_role mr on m.`id` = mr.`mid` Left join role r on r.`id` = mr.`rid` </select></mapper>角色package com.wanqi.pojo;public class Role { private Long id; private String name; private String nameZh; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNameZh() { return nameZh; } public void setNameZh(String nameZh) { this.nameZh = nameZh; } public Role() { } public Role(String name, String nameZh) { this.name = name; this.nameZh = nameZh; } @Override public String toString() { return "Role{" + "id=" + id + ", name='" + name + '\'' + ", nameZh='" + nameZh + '\'' + '}'; }}package com.wanqi.mapper;import com.wanqi.pojo.Role;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import java.util.List;@Mapperpublic interface RoleMapper { /** * 根据用户编号查询角色信息 */ List<Role> getRoleByUserId(@Param("userId") Long userId);}<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wanqi.mapper.RoleMapper"> <select id="getRoleByUserId" parameterType="Long" resultType="com.wanqi.pojo.Role"> select r.id, r.name, r.name_zh as nameZh from role r,user_role ur where r.id = ur.rid and ur.uid= #{userId} </select></mapper>用户package com.wanqi.pojo;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.*;public class User implements UserDetails { private Long id; private String username; private String password; /** 账户是否过期 * 在MySQL中,0被认为是false,非零值被认为是true * */ private Boolean accountNonExpired = true; /** 账户是否激活 */ private Boolean enabled = true; /** 账户是否被锁定 */ private Boolean accountNonLocked = true; /** 密码是否过期 */ private Boolean credentialsNonExpired = true; private List<Role> roles = new ArrayList<>(); public User() { } public User(String username, String password) { this.username = username; this.password = password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Set<SimpleGrantedAuthority> authorities = new HashSet<>(); roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName()))); return authorities; } public Long getId() { return id; } public void setRoles(List<Role> roles) { this.roles = roles; } public List<Role> getRoles() { return roles; } @Override public boolean isAccountNonExpired() { return accountNonExpired; } @Override public boolean isAccountNonLocked() { return accountNonLocked; } @Override public boolean isCredentialsNonExpired() { return credentialsNonExpired; } @Override public boolean isEnabled() { return enabled; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public void setId(Long id) { this.id = id; } public void setAccountNonExpired(Boolean accountNonExpired) { this.accountNonExpired = accountNonExpired; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public void setAccountNonLocked(Boolean accountNonLocked) { this.accountNonLocked = accountNonLocked; } public void setCredentialsNonExpired(Boolean credentialsNonExpired) { this.credentialsNonExpired = credentialsNonExpired; }}package com.wanqi.mapper;import com.wanqi.pojo.User;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;@Mapperpublic interface UserMapper { /** * 根据用户名查询用户 */ User loadUserByUsername(@Param("username") String username); /** * 添加用户 * @param user * @return int */ int save(User user); /** * * 更新密码 * @param password * @param username * @return int */ int updatePassword(@Param("password")String password,@Param("username") String username);}<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wanqi.mapper.UserMapper"> <select id="loadUserByUsername" parameterType="String" resultType="user"> select * from user where username=#{username} </select> <insert id="save" parameterType="user"> INSERT INTO `user` (username, password, accountNonExpired, enabled, accountNonLocked, credentialsNonExpired) VALUES (#{username}, #{password}, #{accountNonExpired}, #{enabled}, #{accountNonLocked}, #{credentialsNonExpired}); </insert> <update id="updatePassword"> UPDATE `user` SET password= #{password} WHERE username = #{username}; </update></mapper>19.5、自定义的资源(url)权限(角色)数据获取类package com.wanqi.security.filter;import cn.hutool.json.JSONUtil;import com.fasterxml.jackson.databind.ObjectMapper;import com.wanqi.mapper.MenuMapper;import com.wanqi.pojo.Menu;import com.wanqi.pojo.Role;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.SecurityConfig;import org.springframework.security.web.FilterInvocation;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import java.util.Collection;import java.util.List;/** * @Description 自定义的资源(url)权限(角色)数据获取类 * @Version 1.0.0 * @Date 2022/9/7 * @Author wandaren */@Componentpublic class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired private MenuMapper menuMapper; @Bean private AntPathMatcher antPathMatcher() { return new AntPathMatcher(); } /** * 获取用户请求的某个具体的资源(url)所需要的权限(角色)集合 */ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { //获取当前请求对象 String requestURI = ((FilterInvocation) object).getRequest().getRequestURI(); //查询所有的菜单 List<Menu> allMenu = menuMapper.getAllMenu(); System.out.println(JSONUtil.toJsonStr(allMenu)); for (Menu menu : allMenu) { if (antPathMatcher().match(menu.getPattern(), requestURI)) { String[] roles = menu.getRoles().stream().map(Role::getName).toArray(String[]::new); System.out.println(JSONUtil.toJsonStr(roles)); return SecurityConfig.createList(roles); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); }}19.6、自定义获取账号信息,与密码自动更新package com.wanqi.service.impl;import cn.hutool.json.JSONUtil;import com.wanqi.mapper.RoleMapper;import com.wanqi.mapper.UserMapper;import com.wanqi.pojo.Role;import com.wanqi.pojo.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsPasswordService;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Component;import java.util.List;@Component("userDetailsImpl")public class UserDetailsImpl implements UserDetailsService, UserDetailsPasswordService { @Autowired private UserMapper userMapper; @Autowired private RoleMapper roleMapper; @Override public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.loadUserByUsername(username); System.out.println("---"+JSONUtil.toJsonStr(user)); if(user == null){ throw new UsernameNotFoundException("用户名不存在"); } List<Role> roles = roleMapper.getRoleByUserId(user.getId()); System.out.println("---"+JSONUtil.toJsonStr(roles)); user.setRoles(roles); return user; } @Override public UserDetails updatePassword(UserDetails user, String newPassword) { int state = userMapper.updatePassword(newPassword, user.getUsername()); if (state == 1) { ((User) user).setPassword(newPassword); } return user; }}19.7、自定义RememberMeServices实现类package com.wanqi.service;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import javax.servlet.http.HttpServletRequest;/** * @Description 自定义RememberMeServices实现类 * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */public class RememberMeServices extends PersistentTokenBasedRememberMeServices { public RememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { super(key, userDetailsService, tokenRepository); } @Override protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { Object attribute = request.getAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER); if (attribute == null) { return false; } String paramValue = attribute.toString(); return paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"); }}19.8、Security配置类package com.wanqi.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.wanqi.security.filter.CustomFilterInvocationSecurityMetadataSource;import com.wanqi.service.RememberMeServices;import com.wanqi.service.impl.UserDetailsImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.ObjectPostProcessor;import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;import org.springframework.security.core.Authentication;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.sql.DataSource;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.UUID;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/5 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig { @Autowired DataSource dataSource; @Autowired CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; UserDetailsImpl userDetailsImpl; @Autowired public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) { this.userDetailsImpl = userDetailsImpl; } @Bean public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Autowired public AuthenticationConfiguration authenticationConfiguration; /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager() throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public PersistentTokenRepository jdbcTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //自动创建表结构,首次启动设置为true// jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Bean public RememberMeServices rememberMeServices(){ return new RememberMeServices(UUID.randomUUID().toString(), userDetailsImpl, jdbcTokenRepository()); } @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { //获取工厂对象 ApplicationContext applicationContext = httpSecurity.getSharedObject(ApplicationContext.class); //设置自定义url权限处理 httpSecurity.apply(new UrlAuthorizationConfigurer<>(applicationContext)) .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource); //是否拒绝公共资源访问 object.setRejectPublicInvocations(false); return object; } }); httpSecurity.formLogin() .and() .logout(logout -> { logout .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"), new AntPathRequestMatcher("/bb", "POST"))) //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(); map.put("msg", "注销成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); } }) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(userDetailsImpl) .rememberMe() .rememberMeServices(rememberMeServices()) .tokenRepository(jdbcTokenRepository()) .and() //禁止csrf跨站请求保护 .csrf().disable(); return httpSecurity.build(); }}20、OAuth220.1、基于gitee实现快速登陆依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>yaml配置spring: security: oauth2: client: registration: gitee: client-id: daf0946aa26c28a661bbfb5bdb89357f8b90e121b53d98ba8b383afd348904e0 client-secret: 1021637c412d22bd2b706f15c0c5c9dad6df859d9f4a01e36b93575b50d98c5c authorization-grant-type: authorization_code redirect-uri: http://localhost:8080/login/oauth2/code/gitee client-name: gitee provider: gitee: authorization-uri: https://gitee.com/oauth/authorize token-uri: https://gitee.com/oauth/token user-info-uri: https://gitee.com/api/v5/user user-name-attribute: namesecurity配置类package com.wanqi.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.oauth2.client.registration.ClientRegistration;import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;import org.springframework.security.oauth2.core.AuthorizationGrantType;import org.springframework.security.web.SecurityFilterChain;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) .oauth2Login(); return http.build(); } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.giteeClientRegistration()); } private ClientRegistration giteeClientRegistration() { return ClientRegistration.withRegistrationId("gitee") .clientId("daf0946aa26c28a661bbfb5bdb89357f8b90e121b53d98ba8b383afd348904e0") .clientSecret("1021637c412d22bd2b706f15c0c5c9dad6df859d9f4a01e36b93575b50d98c5c") .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("http://localhost:8080/login/oauth2/code/gitee") .authorizationUri("https://gitee.com/oauth/authorize") .tokenUri("https://gitee.com/oauth/token") .userInfoUri("https://gitee.com/api/v5/user") .userNameAttributeName("name") .clientName("gitee") .build(); }} (1)client_id、client-secret替换为Gitee获取的数据 (2)authorization-grant-type:授权模式使用授权码模式 (3)redirect-uri:回调地址,填写的与Gitee上申请的一致 (4)client-name:客户端名称,可以在登录选择页面上显示 Gitee的OAuth登录需要自定义provider,Spring Security OAuth提供了配置的方式来实现。 (5)authorization-uri:授权服务器地址 (6)token-uri:授权服务器获取token地址 (7)user-info-uri:授权服务器获取用户信息的地址 (8)user-name-attribute:用户信息中的用户名属性gitee创建第三方应用

20.2、基于内存搭建授权服务器引入依赖,版本使用2.2.5.RELEASE <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>配置securitypackage com.wanqi.config;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.Authentication;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.HashMap;import java.util.Map;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean(value = "bcryptPasswordEncoder") public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Bean public UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) { UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(admin); } @Override protected void configure(HttpSecurity http) throws Exception { //开启权限验证 http.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin().and() //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET //.logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"))) //注销成功后跳转页面 //.logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(); map.put("msg", "注销成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); } }) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) //禁止csrf跨站请求保护 .csrf().disable(); }}自定义 授权服务器配置package com.wanqi.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import javax.annotation.Resource;/** * @Description 自定义 授权服务器配置 * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private PasswordEncoder bcryptPasswordEncoder; /** * 用来配置授权服务器可以为那些客户端授权 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("app") //注册客户端密钥 .secret(bcryptPasswordEncoder.encode("secret")) .redirectUris("https://cn.bing.com") //授权码模式,5选一 .authorizedGrantTypes("authorization_code") //.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token"); //令牌容许获取的资源权限 .scopes("read:user") ; }}请求是否同意授权:http://127.0.0.1:8080/oauth/authorize?client_id=app&redirect_uri=https://cn.bing.com&response_type=code获取令牌:http://app:secret@localhost:8080/oauth/token获取令牌

刷新令牌

修改自定义 授权服务器配置.authorizedGrantTypes("authorization_code","refresh_token") @Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{endpoints.userDetailsService(userDetailsService);}package com.wanqi.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;import javax.annotation.Resource;/** * @Description 自定义 授权服务器配置 * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private PasswordEncoder bcryptPasswordEncoder; @Resource private UserDetailsService userDetailsService; /** * 用来配置授权服务器可以为那些客户端授权 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("app") //注册客户端密钥 .secret(bcryptPasswordEncoder.encode("secret")) .redirectUris("https://cn.bing.com") /* 授权码模式:client_credentials * 刷新令牌:refresh_token * */ .authorizedGrantTypes("authorization_code","refresh_token") //.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token"); //令牌容许获取的资源权限 .scopes("read:user") ; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.userDetailsService(userDetailsService); }}20.3、基于redis搭建授权服务器1、引入依赖,spirng-boot版本2.2.5.RELEASE <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>2、配置redisspring: redis: port: 6379 host: 172.16.156.139 password: qifeng database: 1 #指定数据库3、Security配置类package com.wanqi.config;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.Authentication;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.HashMap;import java.util.Map;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean(value = "bcryptPasswordEncoder") public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(inMemoryUsers(bcryptPasswordEncoder())); } @Override @Bean protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) { UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(admin); } @Override protected void configure(HttpSecurity http) throws Exception { //开启权限验证 http.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 .mvcMatchers("/toLogin").permitAll() .mvcMatchers("/index").permitAll() //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin().and() //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET //.logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"))) //注销成功后跳转页面 //.logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(); map.put("msg", "注销成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); } }) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) //禁止csrf跨站请求保护 .csrf().disable(); }}4、自定义 授权服务器配置package com.wanqi.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;import org.springframework.security.oauth2.provider.token.TokenStore;import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;import javax.annotation.Resource;/** * @Description 自定义 授权服务器配置 * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private PasswordEncoder bcryptPasswordEncoder; @Resource private UserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager ; @Autowired private RedisConnectionFactory redisConnectionFactory ; /** * 用来配置授权服务器可以为那些客户端授权 * * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("app") //注册客户端密钥 .secret(bcryptPasswordEncoder.encode("secret")) .redirectUris("https://cn.bing.com") /* 授权码模式:client_credentials * 简化模式:implicit * 密码模式:password * 客户端模式:client_credentials * 刷新令牌:refresh_token * */ .authorizedGrantTypes("authorization_code", "refresh_token") //.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token"); //令牌容许获取的资源权限 .scopes("read:user") // token的有效期 .accessTokenValiditySeconds(24*3600) // refresh_token的有效期 .refreshTokenValiditySeconds(24*7*3600); super.configure(clients); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.userDetailsService(userDetailsService) .authenticationManager(authenticationManager) .tokenStore(redisTokenStore()); } public TokenStore redisTokenStore(){ return new RedisTokenStore(redisConnectionFactory) ; } /* * 请求是否同意授权:http://127.0.0.1:8080/oauth/authorize?client_id=app&redirect_uri=https://cn.bing.com&response_type=code * 获取令牌:http://app:secret@localhost:8080/oauth/token * */}20.4、基于redis搭建资源服务器1、导入依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>2、redis配置spring: redis: port: 6379 host: 172.16.156.139 password: qifeng database: 1 #指定数据库server: port: 80813、自定义资源服务器配置package com.wanqi.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;import org.springframework.security.oauth2.provider.token.TokenStore;import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;/** * @Description 自定义资源服务器配置 * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableResourceServerpublic class ResourceServerConfigurer extends ResourceServerConfigurerAdapter { @Autowired private RedisConnectionFactory redisConnectionFactory ; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(redisTokenStore()); super.configure(resources); } public TokenStore redisTokenStore(){ return new RedisTokenStore(redisConnectionFactory) ; }}4、模拟资源package com.wanqi.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@RestControllerpublic class HelloController { @RequestMapping("/hello") public String hello(){ return "hello"; }}http://127.0.0.1:8081/hello?access_token=cfa1e9c4-9501-466a-9b87-9ba415bd0821

20.5、基于jwt搭建授权服务器1、依赖导入,版本2.2.5.RELEASE <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>2、Security配置package com.wanqi.config;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.Authentication;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.provisioning.InMemoryUserDetailsManager;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import org.springframework.security.web.util.matcher.OrRequestMatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.HashMap;import java.util.Map;/** * @Description TODO * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean(value = "bcryptPasswordEncoder") public PasswordEncoder bcryptPasswordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(inMemoryUsers(bcryptPasswordEncoder())); } @Override @Bean protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) { UserDetails admin = User.withUsername("admin") .password(encoder.encode("123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(admin); } @Override protected void configure(HttpSecurity http) throws Exception { //开启权限验证 http.authorizeRequests() //permitAll直接放行,必须在anyRequest().authenticated()前面 //anyRequest所有请求都需要认证 .anyRequest().authenticated() .and() //使用form表单验证 .formLogin().and() //注销 .logout(logout -> { logout //指定默认注销url,默认请求方式GET //.logoutUrl("/logout") .logoutRequestMatcher( //自定义注销url new OrRequestMatcher( new AntPathRequestMatcher("/aa", "GET"))) //注销成功后跳转页面 //.logoutSuccessUrl("/toLogin") //前后端分离时代自定义注销登录处理器 .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(); map.put("msg", "注销成功"); map.put("code", HttpStatus.OK.value()); map.put("authentication", authentication); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(s); } }) //销毁session,默认为true .invalidateHttpSession(true) //清除认证信息,默认为true .clearAuthentication(true); }) //指定UserDetailsService来切换认证信息不同的存储方式(数据源) .userDetailsService(inMemoryUsers(bcryptPasswordEncoder())) //禁止csrf跨站请求保护 .csrf().disable(); }}3、jwt内容增强package com.wanqi.config;import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;import org.springframework.security.oauth2.common.OAuth2AccessToken;import org.springframework.security.oauth2.provider.OAuth2Authentication;import org.springframework.security.oauth2.provider.token.TokenEnhancer;import sun.jvm.hotspot.opto.HaltNode;import java.util.HashMap;import java.util.Map;/** * @Description jwt内容增强 * @Version 1.0.0 * @Date 2022/9/9 * @Author wandaren */public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String,Object> map = new HashMap<>(); map.put("test", "jwt内容增强"); ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(map); return accessToken; }}4、授权服务器配置package com.wanqi.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;import org.springframework.security.oauth2.provider.token.TokenEnhancer;import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;import org.springframework.security.oauth2.provider.token.TokenStore;import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;import java.util.ArrayList;import java.util.List;@Configuration// 开启授权服务器的功能@EnableAuthorizationServerpublic class JWTAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; /** * 添加第三方的客户端 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // 第三方客户端的名称 .withClient("app") // 第三方客户端的密钥 .secret(passwordEncoder.encode("secret")) .redirectUris("https://cn.bing.com") /* 授权码模式:client_credentials * 简化模式:implicit * 密码模式:password * 客户端模式:client_credentials * 刷新令牌:refresh_token * */ .authorizedGrantTypes("authorization_code", "refresh_token") //第三方客户端的授权范围 .scopes("all") // token的有效期 .accessTokenValiditySeconds(24 * 3600) // refresh_token的有效期 .refreshTokenValiditySeconds(24 * 7 * 3600); super.configure(clients); } /** * 配置验证管理器,UserdetailService */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { super.configure(endpoints); //配置jwt增强内容 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> list = new ArrayList<>(); list.add(jwtTokenEnhancer()); list.add(jwtAccessTokenConverter()); tokenEnhancerChain.setTokenEnhancers(list); endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService) //设置token 存储策略 .tokenStore(jwtTokenStore()) .accessTokenConverter(jwtAccessTokenConverter()) .tokenEnhancer(tokenEnhancerChain); } /** * jwtTokenStore * * @return */ @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter(); tokenConverter.setSigningKey("name"); return tokenConverter; } @Bean JwtTokenEnhancer jwtTokenEnhancer() { return new JwtTokenEnhancer(); }}20.6、基于jwt搭建资源服务器1、依赖导入,版本2.2.5.RELEASE<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>2、资源服务器配置package com.wanqi.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;import org.springframework.security.oauth2.provider.token.TokenStore;import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;/** * @Description 自定义资源服务器配置 * @Version 1.0.0 * @Date 2022/9/8 * @Author wandaren */@Configuration@EnableResourceServerpublic class JWTResourceServerConfigurer extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { super.configure(resources); resources.resourceId("app") .tokenStore(jwtTokenStore()) .stateless(true); } @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter(); tokenConverter.setSigningKey("name"); return tokenConverter; }}

解析jwt令牌https://jwt.io/

请求资源服务器http://127.0.0.1:8081/hello?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjI3NzU0MjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI2MDhmYWIwOS1jN2NkLTQyOWItYjVhMy0yZmM3YzI1NDU3ZmQiLCJjbGllbnRfaWQiOiJhcHAiLCJzY29wZSI6WyJhbGwiXX0.NpwTkYDmtrNZRNFG5WIvxZqH9FBXhPHSojcCi7GiKfA请求头使用Authorization:Bearer XXXXXX或者使用参数access_token=XXXXXXX

本文链接地址:https://www.jiuchutong.com/zhishi/311705.html 转载请保留说明!

上一篇:phpcms二次开发首页模板在哪里(php二次开发哪个cms)

下一篇:织梦自定义字段option下拉默认值过多无法显示解决方法(织梦自定义模型调用)

  • 小规模纳税人销售已使用固定资产
  • 什么是减免税额90%
  • 幼儿园财务科目设置
  • 个人劳务报酬所得汇算清缴
  • 个体如何申请电子公章流程
  • 出售的固定资产通过固定资产清理吗
  • 房产公司售楼部装修费入什么会计科目
  • 小规模一季度不超过45万
  • 转让房产收取的增值税
  • 合同能源管理项目账务处理
  • 企业丢失账簿稽查局可以处罚吗
  • 不动产抵扣进项税
  • 职工福利费税前扣除限额
  • 进口材料支付的关税
  • 税务分析最常用的分析方法
  • 浙江金税三期个税下载
  • 增值税普票未上传怎么办
  • 餐饮发票可以抵扣成本吗
  • 公司经营情况说明怎么写模板
  • 承兑汇票重复背书
  • 计提增值税的账务处理小规模
  • 增值税一般纳税人是什么意思
  • 分支机构年度终了必须由总机构负责合并汇总纳税吗?
  • 公司地址变更代办需要多少钱
  • 餐厨垃圾处理有哪些设备
  • 外聘人才一次性工资费用入账什么分录?
  • 推荐几个优秀的电影
  • win10声音设置面板在哪
  • avcodec是什么意思
  • Linux Mint Cinnamon中安装MATE桌面详细步骤
  • 应交税费应交增值税转出未交增值税是什么意思
  • 融资租入的设备为什么属于资产
  • 按实际成本结转6日和7日的材料采购成本
  • 育空怀特霍斯附近的北极光,加拿大 (© Design Pics/Danita Delimont)
  • 汇总收款凭证怎么做账
  • 外币交易是什么意思
  • 免费镜像翻转软件
  • php 截断
  • mybitas分页
  • php修改图片尺寸
  • php checkbox使用
  • 营改增抵减的销项税发票要抵扣吗
  • 利润表利息费用包括哪些科目
  • echarts series name
  • 什么时候免交教育费
  • css page-break-after
  • 业务招待费调增额怎么算
  • 一件代发退货如何处理
  • 产生滞纳金有什么影响
  • 帝国cms使用手册
  • php __get()
  • python里面init
  • 单一窗口退税数据发送
  • 教你如何看懂标签
  • 一般纳税人承租个人房屋怎么抵扣
  • 汇算清缴是什么
  • 外资企业取得合法地位
  • 伙食费占支出比例
  • 公司股东为另一家公司全股
  • delete from 表名 where
  • FreeBSD 6.2-RELEASE下载
  • win7怎样关闭ie浏览器
  • windows8桌面设置
  • wind8桌面
  • win10安装完后有多大
  • win8怎么改成win10
  • tf.nn.tanh函数
  • python生成二维码添加图片
  • 使用shell脚本实现自动化软件部署
  • div+css与xhtml+css分别是什么意思?
  • unity自动寻路
  • 百度地图api报错502
  • 安卓无法更新软件
  • 查看流量的命令
  • 开源项目怎么做
  • js初级教程
  • 如何利用Fabric自动化你的任务
  • javascript:download()
  • 营改增后一般纳税人动产租赁税率
  • 国家税务局广东省电子税务局下载
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设