SpringSecurity入门(二)

8、获取用户认证信息

image.png

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

image.png

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());

image.png

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>

image.png

9、 自定义数据源

9.1、 流程分析

image.png
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.

image.png
image.png
image.png

9.2、 修改WebSecurityConfigurer

  @AutowiredDataSource dataSource;@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}public UserDetailsService users(PasswordEncoder encoder) {// The builder will ensure the passwords are encoded before saving in memoryUserDetails 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 Authentication

9.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

image.png

  • 数据库连连配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.0.0.128:3306/testusername: wqpassword: 123456

9.3.3、 修改SecurityFilterChain

  • 通过指定userDetailsService来切换不同的认证数据储存方式
@Configurationpublic class WebSecurityConfigurer {@AutowiredDataSource dataSource;@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}public UserDetailsService inMemoryUsers(PasswordEncoder encoder) {// The builder will ensure the passwords are encoded before saving in memoryUserDetails 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(//自定义注销urlnew 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接口

image.png

9.5、扩展JDBC Authentication之mysql

表设计

image.pngimage.pngimage.png

-- 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: 8081
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.0.0.128:3306/test1username: wqpassword: 123456
mybatis:type-aliases-package: com.wanqi.pojomapper-locations: classpath:mapper/*.xml

9.5.3、编写实体类与Mapper

User
  • User继承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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Set<SimpleGrantedAuthority> authorities = new HashSet<>();roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName())));return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return accountNonExpired;}@Overridepublic boolean isAccountNonLocked() {return accountNonLocked;}@Overridepublic boolean isCredentialsNonExpired() {return credentialsNonExpired;}@Overridepublic 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;}
}
@Mapper
public interface UserMapper {/*** 根据用户名查询用户*/User loadUserByUsername(@Param("username") String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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>
Role
public 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;}
}
@Mapper
public interface RoleMapper {/*** 根据用户编号查询角色信息*/List<Role> getRoleByUserId(@Param("userId") Long userId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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_zhfrom role r,user_role urwhere r.roleId = ur.roleIdand  ur.userId= #{userId}
</select>
</mapper>

9.5.4、实现UserDetailsService接口

@Component("userDetailsImpl")
public class UserDetailsImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleMapper roleMapper;@Overridepublic 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)
@Configurationpublic class MyWebSecurityConfigurer {private UserDetailsImpl userDetailsImpl;@Autowiredpublic 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(//自定义注销urlnew 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、自定义登录处理filter

public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {public JsonLoginFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overridepublic 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
@EnableWebSecurity
public class WebSecurityConfigurer {private UserDetailsImpl userDetailsImpl;@Autowiredpublic void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) {this.userDetailsImpl = userDetailsImpl;}@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}@AutowiredAuthenticationConfiguration authenticationConfiguration;/*** 获取AuthenticationManager(认证管理器),登录时认证使用* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager() throws Exception {return authenticationConfiguration.getAuthenticationManager();}@Beanpublic 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;}@BeanSecurityFilterChain 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(//自定义注销urlnew 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配置类

@Configuration
public class KaptchaConfig {@Beanpublic 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、自定义Filter

public 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);}@Overridepublic 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;@Autowiredpublic 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="@{/vc.img}" alt=""/></label></p><p><input type="submit"></p><p th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></p>
</form></body>
</html>

6、配置WebSecurityConfigurer

	@Configuration@EnableWebSecuritypublic class MyWebSecurityConfigurer {private UserDetailsImpl userDetailsImpl;@Autowiredpublic void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) {this.userDetailsImpl = userDetailsImpl;}@AutowiredAuthenticationConfiguration authenticationConfiguration;@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}/*** 获取AuthenticationManager(认证管理器),登录时认证使用* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager() throws Exception {return authenticationConfiguration.getAuthenticationManager();}public VerifyCodeFilter verifyCodeFilter() throws Exception {VerifyCodeFilter filter = new VerifyCodeFilter(authenticationManager());// 自定义接收用户名的参数名为unamefilter.setUsernameParameter("uname");//自定义接收密码的参数名为pwdfilter.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(//自定义注销urlnew 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;@Autowiredpublic 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、自定义Filter

public 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);}@Overridepublic 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
@EnableWebSecurity
public class WebSecurityConfigurer {private UserDetailsImpl userDetailsImpl;@Autowiredpublic void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) {this.userDetailsImpl = userDetailsImpl;}@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}@AutowiredAuthenticationConfiguration authenticationConfiguration;/*** 获取AuthenticationManager(认证管理器),登录时认证使用* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager() throws Exception {return authenticationConfiguration.getAuthenticationManager();}@Beanpublic 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;}@BeanSecurityFilterChain 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(//自定义注销urlnew 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();}
}

相关文章

SpringSecurity入门(一)

SpringSecurity入门(三)

SpringSecurity入门(四)

未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/850973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最新下载:Navicat for MySQL 11软件安装视频教程

软件简介&#xff1a; Navicat for MySQL 是一款强大的 MySQL 数据库管理和开发工具&#xff0c;它为专业开发者提供了一套强大的足够尖端的工具&#xff0c;但对于新用户仍然易于学习。Navicat For Mysql中文网站&#xff1a;http://www.formysql.com/ Navicat for MySQL 基于…

NLP实战入门——文本分类任务(TextRNN,TextCNN,TextRNN_Att,TextRCNN,FastText,DPCNN,BERT,ERNIE)

本文参考自https://github.com/649453932/Chinese-Text-Classification-Pytorch?tabreadme-ov-file&#xff0c;https://github.com/leerumor/nlp_tutorial?tabreadme-ov-file&#xff0c;https://zhuanlan.zhihu.com/p/73176084&#xff0c;是为了进行NLP的一些典型模型的总…

如何远程桌面连接?

远程桌面连接是一种方便快捷的方式&#xff0c;可以帮助用户在不同地区的设备之间实现信息的远程通信。我们将介绍一种名为【天联】的组网产品&#xff0c;它可以帮助用户轻松实现远程桌面连接。 【天联】组网是一款异地组网内网穿透产品&#xff0c;由北京金万维科技有限公司…

绿联Nas docker 中 redis 老访问失败的排查

部署了一些服务&#xff0c;老隔3-5 天其他服务就联不上 redis 了&#xff0c;未确定具体原因&#xff0c;只记录观察到的现象 宿主机访问 只有 ipv6 绑定了&#xff0c;ipv4 绑定挂掉了 其他容器访问 也无法访问成功 当重启容器后&#xff1a; 一切又恢复正常。 可能的解…

MATLAB | 透明度渐变颜色条

hey 各位好久不见&#xff0c;今天提供一段有趣的小代码&#xff0c;之前刷到公众号闻道研学的一篇推送MATLAB绘图技巧 | 设置颜色条的透明度&#xff08;附完整代码&#xff09;&#xff08;https://mp.weixin.qq.com/s/bVx8AVL9jGlatja51v4H0A&#xff09;&#xff0c;文章希…

机器学习周记(第四十二周:AT-LSTM)2024.6.3~2024.6.9

目录 摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构3.1 LSTM3.2 注意力机制概述3.3 AT-LSTM3.4 数据预处理 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 训练参数4.3.2 数据集4.3.3 实验设置4.3.4 实验结果 5. 基于pytorch的transformer 摘要 本周阅读…

免费,C++蓝桥杯等级考试真题--第11级(含答案解析和代码)

C蓝桥杯等级考试真题--第11级 答案&#xff1a;D 解析&#xff1a; A. a b; b a; 这种方式会导致a和b最终都等于b原来的值&#xff0c;因为a的原始值在被b覆盖前没有保存。 B. swap(a&#xff0c;b); 如果没有自定义swap函数或者没有包含相应的库&#xff0c;这个选项会编…

【C++题解】1389 - 数据分析

问题&#xff1a;1389 - 数据分析 类型&#xff1a;简单循环 题目描述&#xff1a; 该方法的操作方式为&#xff0c;如果要传递 2 个数字信息给友军&#xff0c;会直接传递给友军一个整数 n&#xff08;n 是一个 10 位以内的整数&#xff09;&#xff0c;该整数的长度代表要传…

汇编语言LDS指令

在8086架构的实模式下&#xff0c;LDS指令&#xff08;Load Pointer Using DS&#xff09;用于从内存中加载一个32位的指针到指定寄存器和DS寄存器。我们来详细解释一下这条指令为什么会修改DS段寄存器。 LDS指令的功能 LDS指令格式如下&#xff1a; LDS destination, sourc…

程序猿大战Python——运算符

常见的运算符 目标&#xff1a;了解Python中常见的运算符有哪些&#xff1f; 运算符是用于执行程序代码的操作运算。常见的运算符有&#xff1a; &#xff08;1&#xff09;算术运算符&#xff1a;、-、*、/、//、% 、**&#xff1b; &#xff08;2&#xff09;赋值运算符&am…

macOS - 终端快捷键

本文转自 Mac 上“终端”中的键盘快捷键 https://support.apple.com/zh-cn/guide/terminal/trmlshtcts/mac 以下基于系统版本 macOS Sonoma 14 文章目录 Mac 上“终端”中的键盘快捷键1、使用“终端”窗口和标签页2、编辑命令行3、在“终端”窗口中选择和查找文本4、使用标记和…

【Uniapp】uniapp微信小程序定义图片地址全局变量

错误写法&#xff1a; main.js Vue.prototype.$imgUrl 图片地址这么写之后 就发现压根不起作用&#xff1b;获取到的是undefined 正确写法&#xff1a; 返回函数&#xff0c;后面可以拼上OSS图片完整路径 Vue.prototype.$imgUrl (url) > {return ("https://地址…

Android——热点开关(优化中)

SoftAP打开与关闭 目录 1.三个名词的解释以及关系 Tethering——网络共享&#xff0c;WiFi热点、蓝牙、USB SoftAp——热点(无线接入点)&#xff0c;临时接入点 Hostapd——Hostapd是用于Linux系统的软件&#xff0c;&#xff0c;支持多种无线认证和加密协议&#xff0c;将任…

LabVIEW进行图像拼接的实现方法与优化

在工业检测和科研应用中&#xff0c;对于大尺寸物体的拍摄需要通过多次拍摄后进行图像拼接。LabVIEW 作为强大的图形化编程工具&#xff0c;能够实现图像拼接处理。本文将详细介绍LabVIEW进行图像拼接的实现方法、注意事项和提高效率的策略。 图像拼接的实现方法 1. 图像采集…

c++引用的本质(反汇编角度分析)

目录 一、引用基础理论 二、 引用的本质 三、从反汇编角度进行分析 1.变量赋值 2.引用和指针初始化 3.通过引用和指针赋值 4.eaxd的作用 一、引用基础理论 在c中我们都知道&#xff0c;引用&#xff08;&&#xff09;就是变量的一个别名&#xff0c;它允许我们为已存…

Python魔法之旅专栏(导航)

目录 推荐阅读 1、Python筑基之旅 2、Python函数之旅 3、Python算法之旅 4、博客个人主页 首先&#xff0c;感谢老铁们一直以来对我的支持与厚爱&#xff0c;让我能坚持把Python魔法方法专栏更新完毕&#xff01; 其次&#xff0c;为了方便大家查阅&#xff0c;我将此专栏…

C#操作MySQL从入门到精通(21)——删除数据

前言: 谈到数据库,大家最容易脱口而出的就是增删改查,本文就是来详细介绍如何删除数据。 本文测试使用的数据库如下: 1、删除部分数据 使用delete 关键字,并且搭配where条件使用,否则会导致表中数据全部被删除 string sql = string.Empty;if (radioButton_DeletePart…

保存图片奇怪的bug

今天发现一个奇怪的bug 这个的dpi是100de ,但是我取完切片之后&#xff0c;发现这个结果就变了

Vivado时序报告之Datasheet详解

目录 一、前言 二、Datasheet配置选项说明 2.1 Options 2.2 Groups 2.3 Timer Settings 2.4 Common Options 三、Datasheet报告 3.1 General Information 3.2 Input Ports Setup/Hold 3.3 Output Ports Clock-to-out 3.4 Setup between Clocks 3.5 Combinational…