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.rhkb.cn/news/346908.html

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

相关文章

图形和插图软件Canvas X Pro 20 Build 914

Canvas X Pro是一款功能强大、用途广泛的Windows软件,旨在处理技术图形和可视化,该程序结合了创建矢量和光栅图形的工具,这使其成为需要创建高质量技术插图和演示文稿的工程师、设计师、科学家和其他专业人士的理想选择。 Canvas X Pro的主要功能之一是支持处理大型和复杂的…

tcp协议的延迟应答(介绍+原则),拥塞控制(拥塞窗口,网络出现拥塞时,滑动窗口的大小如何确定,慢启动,阈值)

目录 延迟应答 引入 介绍 原则 拥塞控制 引入 网络出现拥塞 引入 介绍 介绍 拥塞窗口 介绍 决定滑动窗口的大小 慢启动 介绍 为什么要有慢启动 阈值 算法 总结 延迟应答 引入 发送方一次发送更多的数据,发送效率就越高 因为要写入网卡硬件的io速度很慢,尽量…

笔记 | 软件工程06-2:软件设计-软件体系结构设计

1 软件体系结构的概念 1.1 软件体系结构的设计元素 1.2 不同的抽象层次 1.3 软件体系结构的不同视图 1.3.1 软件体系结构的逻辑视图&#xff1a;包图 1.3.2 软件体系结构的逻辑视图&#xff1a;构件图 1.3.3 软件体系结构的开发视图 1.3.4 软件体系结构的部署视图 1.3.4.1 描述…

02眼电识别眼动--软件V1.0

对应视频链接点击直达 01项目点击下载&#xff0c;可直接运行&#xff08;含数据库&#xff09; 02眼电识别眼动--软件V1.0 对应视频链接点击直达构思结语其他以下是废话 构思 对于软件&#xff0c;主要就是接收数据、处理数据、储存和显示数据。 这是主要页面&#xff0c;…

【第2章】Vue快速上手

文章目录 前言一、第一个Vue程序二、Open in Browser插件1.安装2. 使用3. 界面 总结 前言 这里我们来实现我们的第一个程序。 一、第一个Vue程序 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name&quo…

java版UWB高精度实时定位系统源码springboot+vue

UWB人员定位系统&#xff0c;实现人员的自动识别、位置定位、区域报警等功能。该系统能高效记录人员信息&#xff0c;出入信息及位置信息&#xff0c;并能灵活的查询及管理历史轨迹&#xff0c;可极大提高信息安全度&#xff0c;有效弥补了视频监控的不足。使人员管理实现信息化…

【端午惊喜】2024年6月6日 docker 国内镜像源集体失效

文章目录 概述中科大镜像源阿里镜像源其他镜像源可用的镜像源写在最后 概述 大家都知道使用docker hub官方镜像需要魔法&#xff0c;虽然大部人有魔法&#xff0c;但是网速也是很慢&#xff0c;还有部分同学没有&#xff0c;全靠国内各大厂商的镜像源&#xff0c;可是端午6.6大…

一个公用的数据状态修改组件

灵感来自于一项重复的工作&#xff0c;下图中&#xff0c;这类禁用启用、审核通过不通过、设计成是什么状态否什么状态的场景很多。每一个都需要单独提供接口。重复工作还蛮大的。于是&#xff0c;基于该组件类捕获组件跳转写了这款通用接口。省时省力。 代码如下&#xff1a;…

LabVIEW程序内存泄漏分析与解决方案

维护他人编写的LabVIEW程序时&#xff0c;若发现程序运行时间越长&#xff0c;占用内存越大直至崩溃&#xff0c;通常是内存泄漏导致的。本文从多角度分析内存泄漏的可能原因&#xff0c;包括数组和字符串处理、未释放的资源、循环中的对象创建等&#xff0c;并提供具体的解决方…

【ARM Coresight Debug 系列 -- ARMv8/v9 软件实现断点地址设置】

请阅读【嵌入式开发学习必备专栏 】 文章目录 ARMv8/v8 软件设置段带你断点地址软件配置流程代码实现 ARMv8/v8 软件设置段带你 在ARMv8/9架构中&#xff0c;可以通过寄存器 DBGBVR0_EL1 设置断点。这个寄存器是一系列调试断点值寄存器中的第一个DBGBVRn_EL1&#xff0c;其中n…

http接口上传文件响应413:413 Request Entity Too Large

目录 一、场景简介二、异常展示三、原因四、解决 一、场景简介 1、服务端有经过nginx代理 2、上传文件超过5M时&#xff0c;响应码为413 3、上传文件小于5M时&#xff0c;上传正常 二、异常展示 三、原因 nginx限制了上传数据的大小 四、解决 扩大nginx上传数据的大小 步…

大模型生成短视频

最近看到一个开源项目可以通过AI生成短视频&#xff0c;然后尝试了下&#xff0c;感觉还不错&#xff0c;下面是具体步骤。 项目名叫moneyprinterTurbo&#xff0c;它本意是对接到Youtube&#xff0c;自动生成视频并上传到Youtube获取流量赚钱&#xff0c;所以项目名叫moneypri…

CISP究竟适合谁?这四类人没跑了

在信息技术飞速发展的现在&#xff0c;网络安全已经成为了一个不可忽视的话题。 CISP&#xff0c;即注册信息安全专业人员&#xff0c;是网络安全领域内一项备受认可的专业认证。 但CISP究竟适合谁考呢&#xff1f;这不仅是一个技术问题&#xff0c;更是一个职业规划的问题。…

修改onnx模型中间节点命名(包含输入、输出重命名)

来源&#xff1a;Paddle2ONNX Paddle2ONNX/tools/onnx/README.md at develop PaddlePaddle/Paddle2ONNX GitHub 依赖&#xff1a;import onnx python rename_onnx_model.py --model model.onnx --origin_names x y z --new_names x1 y1 z1 --save_file new_model.onnx 其中 …

Windows 10 找不到Microsoft Edge 浏览器

下载链接 了解 Microsoft Edge 手动下载浏览器 问题说明 一般来说&#xff0c;windows10系统应该是自带浏览器edge的&#xff0c;但有的电脑就是没有找到edge浏览器&#xff0c;可能系统是精简过的&#xff0c;可能是被卸载了。如下&#xff0c;控制面板确实没找到程序。 ​ …

修改SubVI的LabVIEW默认搜索路径

在启动顶级VI后&#xff0c;LabVIEW可能会遇到找不到subVI的情况。这通常是由于subVI的路径发生了变化或没有被正确配置。 LabVIEW默认搜索路径 默认情况下&#xff0c;LabVIEW会按以下顺序搜索文件位置&#xff08;*表示LabVIEW将搜索子目录&#xff09;&#xff1a; <t…

基于SSM+Jsp的交通事故档案管理系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

3.Nginx配置文件基本介绍

nginx配置文件所在路径&#xff1a;/usr/local/nginx/conf/nginx.conf nginx配置文件有三块&#xff1a; 1.全局块 从配置文件开始到events块之间的内容&#xff0c;主要会设置一些影响nginx服务器整体运行的配置指令。 配置运行nginx服务器的用户(组)允许生成的worker pro…

hadoop和hbase对应版本关系

https://hbase.apache.org/book.html#configuration

Vulnhub-DC-3

joomla3.7.0的提权 靶机IP:192.168.20.136 kaliIP:192.168.20.128 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) 信息收集 首先nmap扫端口和版本&#xff0c;dirsearch跑下目录&#xff0c;wappalyzer也可以用下 发现服务器用的ubuntu&#xff0c;JoomlaCMS等…