Srping Security学习

2023-11-22 14:58
文章标签 学习 security srping

本文主要是介绍Srping Security学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Srping Security学习

1、简单的HttpBasic模式

在简单的HttpBasic模式下,启动项目的时候在控制台可以看到一串base64加密的密码,用户名默认是user

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@ResourceMyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@ResourceMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {//简单httpbasic验证http.httpBasic().and().authorizeRequests().anyRequest().authenticated();}
}

指定HttpBasic的用户名和密码

spring:security:user:name: adminpassword: admin

2、formLogin登录验证模式

在formLogin模式下,可以定制登录认证,授权认证,登录页面等等大量的操作。

1.0版本表单验证

SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@ResourceMyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@ResourceMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {//formlogin验证http.csrf().disable().formLogin().loginPage("/login.html")//指定登录页面.usernameParameter("username")//可以指定前端页面的用户名参数.passwordParameter("password")//可以指定前端页面的密码参数.loginProcessingUrl("/login")//指定登录URL
//                    .defaultSuccessUrl("/index")//指定登录成功的跳转页面
//                    .failureUrl("/login.html").and().authorizeRequests()//需要权限的请求.antMatchers("/login.html","/login")//添加权限的请求.permitAll()//允许所有访问.antMatchers("/biz1","/biz2").hasAnyAuthority("ROLE_user","ROLE_admin")//有xxx权限才能访问.antMatchers("/syslog","/sysuser")
//                        .hasAnyAuthority("sys:long")//有这个资源id才能访问.hasRole("admin")//有什么角色才能访问.anyRequest().authenticated();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("123456")).roles("user").and().withUser("admin").password(passwordEncoder().encode("123456"))
//                .authorities("sys:log", "sys:user")//资源ID.roles("admin").and().passwordEncoder(passwordEncoder());//配置BCrypt加密}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overridepublic void configure(WebSecurity web) throws Exception {//将项目中的静态资源路径开放web.ignoring().antMatchers("/css/**", "/fonts/**", "/img/**");}

2.0版本表单验证(增强登录成功跳转)

在1.0的基础上定制了成功页面跳转,错误页面跳转

SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@ResourceMyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@ResourceMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {//formlogin验证http.csrf().disable().formLogin().loginPage("/login.html")//指定登录页面.usernameParameter("username")//可以指定前端页面的用户名参数.passwordParameter("password")//可以指定前端页面的密码参数.loginProcessingUrl("/login")//指定登录URL
//                    .defaultSuccessUrl("/index")//指定登录成功的跳转页面
//                    .failureUrl("/login.html").successHandler(myAuthenticationSuccessHandler)//自定义的成功处理器.failureHandler(myAuthenticationFailureHandler)//自定义的错误处理器.and().authorizeRequests()//需要权限的请求.antMatchers("/login.html","/login")//添加权限的请求.permitAll()//允许所有访问.antMatchers("/biz1","/biz2").hasAnyAuthority("ROLE_user","ROLE_admin")//有xxx权限才能访问.antMatchers("/syslog","/sysuser")
//                        .hasAnyAuthority("sys:long")//有这个资源id才能访问.hasRole("admin")//有什么角色才能访问.anyRequest().authenticated();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("123456")).roles("user").and().withUser("admin").password(passwordEncoder().encode("123456"))
//                .authorities("sys:log", "sys:user")//资源ID.roles("admin").and().passwordEncoder(passwordEncoder());//配置BCrypt加密}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overridepublic void configure(WebSecurity web) throws Exception {//将项目中的静态资源路径开放web.ignoring().antMatchers("/css/**", "/fonts/**", "/img/**");}

MyAuthenticatioSuccessHandler(成功页面跳转)

@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {@Value("${spring.security.loginType}")private String loginType;private static ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication) throws ServletException, IOException {if(loginType.equalsIgnoreCase("JSON")){response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(AJaxResponse.success("/index")));}else {//跳转登录之前请求的页面super.onAuthenticationSuccess(request, response, authentication);}}}

MyAuthenticationFailureHandler(错误页面定制器)

@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {@Value("${spring.security.loginType}")private String loginType;private static ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response,AuthenticationException exception)throws IOException, ServletException {String errorMsg = "用户名或者密码输入错误";if (exception instanceof SessionAuthenticationException) {errorMsg = exception.getMessage();}if(loginType.equalsIgnoreCase("JSON")){response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(AJaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, errorMsg))));}else {//跳转登录之前请求的页面response.setContentType("text/html;charset=UTF-8");super.onAuthenticationFailure(request, response, exception);}}
}

AJaxResponse

public class AJaxResponse {private boolean isok;private int code;private String message;private Object data;public boolean isIsok() {return isok;}public void setIsok(boolean isok) {this.isok = isok;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public AJaxResponse() {}public static AJaxResponse success(){AJaxResponse resultBean = new AJaxResponse();resultBean.setIsok(true);resultBean.setCode(200);resultBean.setMessage("success");return resultBean;}public static AJaxResponse success(Object data){AJaxResponse resultBean = new AJaxResponse();resultBean.setIsok(true);resultBean.setCode(200);resultBean.setMessage("success");resultBean.setData(data);return resultBean;}public static AJaxResponse error(CustomException e){AJaxResponse resultBean = new AJaxResponse();resultBean.setIsok(false);resultBean.setCode(e.getCode());if (e.getCode() == CustomExceptionType.USER_INPUT_ERROR.getCode()){resultBean.setMessage(e.getMessage());}else if (e.getCode() == CustomExceptionType.SYSTEM_ERROR.getCode()){resultBean.setMessage(e.getMessage() + ",系统出现异常");}else{resultBean.setMessage("系统出现未知异常");}return resultBean;}
}

CustomException

public class CustomException {private int code;private String message;private CustomExceptionType customExceptionType;public CustomException(CustomExceptionType customExceptionType, String message) {this.message = message;this.customExceptionType = customExceptionType;this.code = customExceptionType.getCode();}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public CustomExceptionType getCustomExceptionType() {return customExceptionType;}public void setCustomExceptionType(CustomExceptionType customExceptionType) {this.customExceptionType = customExceptionType;}}

CustomExceptionType

public enum  CustomExceptionType {USER_INPUT_ERROR(400,"用户输入异常"),SYSTEM_ERROR(500,"系统服务异常"),OTHER_ERROR(999,"其他未知异常");private int code;private String typeDesc;CustomExceptionType(int code, String typeDesc) {this.code = code;this.typeDesc = typeDesc;}public int getCode() {return code;}public String getTypeDesc() {return typeDesc;}
}

application.yml

指定放回页面的数据类型

spring:security:loginType: JSON

3.0版本表单验证(增强session管理)

增强部分:

1.创建session的方式,使用默认方法,如果有需要就创建session

2.session超时处理,跳转到指定页面

3.session安全配置,每次登录都替换sessionID

4.最多允许一个登录,并返回定制的消息MyExpiredSessionStrategy

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)//如果有需要就创建session.invalidSessionUrl("/login.html")//当session超时,重新跳转到指定页面.sessionFixation().migrateSession()//每次登录都替换sesionID.maximumSessions(1)//只允许最多一个登录.maxSessionsPreventsLogin(false)//允许登录之后再登录.expiredSessionStrategy(new MyExpiredSessionStrategy())//返回自定义的配置

SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@ResourceMyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@ResourceMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {//formlogin验证http.csrf().disable().formLogin().loginPage("/login.html")//指定登录页面.usernameParameter("username")//可以指定前端页面的用户名参数.passwordParameter("password")//可以指定前端页面的密码参数.loginProcessingUrl("/login")//指定登录URL
//                    .defaultSuccessUrl("/index")//指定登录成功的跳转页面
//                    .failureUrl("/login.html").successHandler(myAuthenticationSuccessHandler)//自定义的成功处理器.failureHandler(myAuthenticationFailureHandler)//自定义的错误处理器.and().authorizeRequests()//需要权限的请求.antMatchers("/login.html","/login")//添加权限的请求.permitAll()//允许所有访问.antMatchers("/biz1","/biz2").hasAnyAuthority("ROLE_user","ROLE_admin")//有xxx权限才能访问.antMatchers("/syslog","/sysuser")
//                        .hasAnyAuthority("sys:long")//有这个资源id才能访问.hasRole("admin")//有什么角色才能访问.anyRequest().authenticated().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)//如果有需要就创建session.invalidSessionUrl("/login.html")//当session超时,重新跳转到指定页面.sessionFixation().migrateSession()//每次登录都替换sesionID.maximumSessions(1)//只允许最多一个登录.maxSessionsPreventsLogin(false)//允许登录之后再登录.expiredSessionStrategy(new MyExpiredSessionStrategy())//返回自定义的配置;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("123456")).roles("user").and().withUser("admin").password(passwordEncoder().encode("123456"))
//                .authorities("sys:log", "sys:user")//资源ID.roles("admin").and().passwordEncoder(passwordEncoder());//配置BCrypt加密}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overridepublic void configure(WebSecurity web) throws Exception {//将项目中的静态资源路径开放web.ignoring().antMatchers("/css/**", "/fonts/**", "/img/**");}

MyExpiredSessionStrategy

public class MyExpiredSessionStrategy implements SessionInformationExpiredStrategy {private static ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException, ServletException {Map<String, Object> map = new HashMap<>();map.put("code",0);map.put("msg","你不在别处登录了");sessionInformationExpiredEvent.getResponse().setContentType("application/json;charset=UTF-8");sessionInformationExpiredEvent.getResponse().getWriter().write(objectMapper.writeValueAsString(map));}
}

4.0增强版本(动态加载权限)

要实现登录动态加载权限,首先要实现UserDetails接口和UserDetailsService接口,在UserDetailsService有个方法UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;,这个方法主要是登录的时候帮我们给UserDetails赋值,比如username,password等等,这样spring security才能得到登录用户的信息。

MyUserDetails(继承UserDetails,让spring security用我们自己的UserDetails)

public class MyUserDetails implements UserDetails {private String password;//密码private String username;//用户名private boolean accountNonExpired;//是否没过期private boolean accountNonLocked;//是否没被锁定private boolean credentialNonExpired;//是否没过期private boolean enabled;//账号是否可用Collection<? extends GrantedAuthority> authorities;//用户的权限集合@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}public void setPassword(String password) {this.password = password;}public void setUsername(String username) {this.username = username;}public void setAccountNonExpired(boolean accountNonExpired) {this.accountNonExpired = accountNonExpired;}public void setAccountNonLocked(boolean accountNonLocked) {this.accountNonLocked = accountNonLocked;}public void setCredentialNonExpired(boolean credentialNonExpired) {this.credentialNonExpired = credentialNonExpired;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {this.authorities = authorities;}
}

MyUserDetailsService(继承UserDetailsService,实现loadUserByUsername,给自定义的MyUserDetails赋值)

public interface MyUserDetailsService extends UserDetailsService {//根据userID查询用户信息MyUserDetails findByUserName(String userId);//根据userID查询你用户角色List<String> findRoleByUserName(String userId);//根据用户角色查询用户权限List<String> findAuthorityByRoleCodes(List<String> roleCodes);
}

MyUserDetailsServiceImpl(继承MyUserDetailsService接口,实现loadUserByUsername,给自定义的MyUserDetails赋值)

@Service
public class MyUserDetailsServiceImpl implements MyUserDetailsService {@Autowiredprivate MyUserDetailsMapper myUserDetailsMapper;@Overridepublic MyUserDetails findByUserName(String userId) {return myUserDetailsMapper.findByUserName(userId);}@Overridepublic List<String> findRoleByUserName(String userId) {return myUserDetailsMapper.findRoleByUserName(userId);}@Overridepublic List<String> findAuthorityByRoleCodes(List<String> roleCodes) {return myUserDetailsMapper.findAuthorityByRoleCodes(roleCodes);}@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//加载基础用户信息MyUserDetails myUserDetails = myUserDetailsMapper.findByUserName(s);//加载用户角色列表List<String> roleCodes = myUserDetailsMapper.findRoleByUserName(s);//通过用户角色列表加载用户的资源权限List<String> authoritys = myUserDetailsMapper.findAuthorityByRoleCodes(roleCodes);//角色是一个特殊的权限,ROLE_前缀roleCodes = roleCodes.stream().map(rc -> "ROLE_" + rc).collect(Collectors.toList());authoritys.addAll(roleCodes);myUserDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",",authoritys)));return myUserDetails;}
}

MyUserDetailsMapper

@Mapper
@Repository
public interface MyUserDetailsMapper {//根据userID查询用户信息MyUserDetails findByUserName(@Param("userId") String userId);//根据userID查询你用户角色List<String> findRoleByUserName(@Param("userId") String userId);//根据用户角色查询用户权限List<String> findAuthorityByRoleCodes(@Param("roleCodes") List<String> roleCodes);
}

MyUserDetailsMapper.xml(当返回接口是List的时候,List里面是什么类型,returntype就是什么型)

<?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="cn.edu.lingnan.mapper.MyUserDetailsMapper"><select id="findByUserName" resultType="MyUserDetails">select username,password,enabledfrom sys_userwhere username = #{userId}</select><select id="findRoleByUserName" resultType="java.lang.String">select role_codefrom sys_role rleft join sys_user_role ur on r.id = ur.role_idleft join sys_user u on u.id = ur.user_idwhere u.username = #{userId}</select><select id="findAuthorityByRoleCodes" resultType="java.lang.String">select urlfrom sys_menu mleft join sys_role_menu rm on m.id = rm.menu_idleft join sys_role r on r.id = rm.role_idwhere r.role_code in<foreach collection="roleCodes" item="roleCode" open="(" separator="," close=")">#{roleCode}</foreach></select>
</mapper>

----------------------------------------------------上面实现登录认证功--------------------------------------------------------------------

----------------------------------------------------下面实现动态加载权限----------------------------------------------------------------

这里我不是理解的很透彻,为什么还要查询一次url,只理解动态匹配url,所有先贴上代码吧。

MyRBACService

public interface MyRBACService {public boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

MyRBACServiceImpl(之前在SercurityConfig的路径匹配是写死的,这里是通过查询动态匹配)

@Component("rbacService")
public class MyRBACServiceImpl implements MyRBACService {private AntPathMatcher antPathMatcher = new AntPathMatcher();@Autowiredprivate MyRBACServiceMapper myRBACServiceMapper;@Overridepublic boolean hasPermission(HttpServletRequest request, Authentication authentication) {Object principal = authentication.getPrincipal();if(principal instanceof UserDetails){String username = ((UserDetails)principal).getUsername();List<String> urls = myRBACServiceMapper.findUrlsByUserName(username);//通过查询到的url来匹配我们可以访问的urlreturn urls.stream().anyMatch(url ->antPathMatcher.match(url,request.getRequestURI()));}return false;}
}

MyRBACServiceMapper

@Mapper
@Repository
public interface MyRBACServiceMapper {List<String> findUrlsByUserName(@Param("userId") String userId);
}

MyRBACServiceMapper.xml

<?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="cn.edu.lingnan.mapper.MyRBACServiceMapper"><select id="findUrlsByUserName" resultType="java.lang.String">select urlfrom sys_menu mleft join sys_role_menu rm on m.id = rm.menu_idleft join sys_role r on r.id = rm.role_idleft join sys_user_role ur on r.id = ur.role_idleft join sys_user u on u.id = ur.user_idwhere u.username = #{userId}</select>
</mapper>

-----------------上面认证和授权的配置都写好之后,我们就可以在SecurityConfig加入我们的配置了------------------

SecurityConfig(把我们的认证和配置加进去,修改configure的两个方法)

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredMyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@AutowiredMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Autowiredprivate MyUserDetailsServiceImpl myUserDetailsService;@Overrideprotected void configure(HttpSecurity http) throws Exception {//formlogin验证http.csrf().disable().formLogin().loginPage("/login.html")//指定登录页面.usernameParameter("username")//可以指定前端页面的用户名参数.passwordParameter("password")//可以指定前端页面的密码参数.loginProcessingUrl("/login")//指定登录URL.successHandler(myAuthenticationSuccessHandler)//自定义的成功处理器.failureHandler(myAuthenticationFailureHandler)//自定义的错误处理器.and().authorizeRequests()//需要权限的请求.antMatchers("/login.html","/login").permitAll()//允许所有访问.antMatchers("/index").authenticated().anyRequest().access("@rbacService.hasPermission(request,authentication)").and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)//如果有需要就创建session.invalidSessionUrl("/login.html")//当session超时,重新跳转到指定页面.sessionFixation().migrateSession()//每次登录都替换sesionID.maximumSessions(1)//只允许最多一个登录.maxSessionsPreventsLogin(false)//允许登录之后再登录.expiredSessionStrategy(new MyExpiredSessionStrategy())//返回自定义的配置;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overridepublic void configure(WebSecurity web) throws Exception {//将项目中的静态资源路径开放web.ignoring().antMatchers("/css/**", "/fonts/**", "/img/**");}
}

3、Remember-Me(记住密码)

简单remember-me功能

SecurityConfig

.rememberMe().rememberMeParameter("remember-me")//设置前端属性名称.rememberMeCookieName("remember-me-cookie")//设置cookie名称.tokenValiditySeconds(7 * 24 * 60 * 60)//设置过期时间

前端页面,如果不设置rememberMeParameter,一定要设置name为remember-me

<form action="/login" method="post"><span>用户名称</span><input type="text" name="username"><br><span>用户密码</span><input type="password" name="password"><br><input type="submit" value="登录"><label><input type="checkbox" name="remember-me">记住密码</label>
</form>

增强remember-me功能,重启服务不影响cookie

建表

CREATE TABLE `persistent_logins`  (`username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`series` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`token` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`last_used` timestamp NULL DEFAULT NULL,PRIMARY KEY (`series`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = gbk_chinese_ci ROW_FORMAT = Compact;

给容器加入PersistentTokenRepository

@Bean
public PersistentTokenRepository persistentTokenRepository(){JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);return tokenRepository;
}

SecurityConfig配置token

.rememberMe().rememberMeParameter("remember-me")//设置前端属性名称.rememberMeCookieName("remember-me-cookie")//设置cookie名称.tokenValiditySeconds(7 * 24 * 60 * 60)//设置过期时间.tokenRepository(persistentTokenRepository())//存储token到数据库,应用重新启动不会刷掉token

4、退出功能

SecurityConfig配置logout

http.logout().logoutUrl("/signout")//设置退出url.logoutSuccessUrl("/login.html")//设置退出成功页面.deleteCookies("JSESSIONID")//删除JSESSIONID.logoutSuccessHandler(myLogoutSuccessHandler)//自定义退出处理器

前端配置退出url

<a href="/signout">退出</a><br>

自定义LogoutSuccessHandler

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication)throws IOException, ServletException {//写一下业务逻辑httpServletResponse.sendRedirect("/login.html");}
}

5、基于session的图片验证

配置kaptcha类

引入依赖

<!--t图片验证-->
<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version><exclusions><exclusion><artifactId>javax.servlet-api</artifactId><groupId>javax.servlet</groupId></exclusion></exclusions>
</dependency>
<!--工具类-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8</version>
</dependency>

kaptcha属性文件

kaptcha.border=no
kaptcha.border.color=105,179,90
kaptcha.image.width=100
kaptcha.image.height=45
kaptcha.seesion.key=code
kaptcha.textproducer.font.color=blue
kaptcha.textproducer.font.size=35
kaptcha.textproducer.char.length=4
kaptcha.textproducer.font.names=宋体,楷体,微软雅黑

CaptchaConfig

@Configuration
@PropertySource(value = {"classpath:kaptcha.properties"})
public class CaptchaConfig {@Value("${kaptcha.border}")private String border;@Value("${kaptcha.border.color}")private String borderColor;@Value("${kaptcha.textproducer.font.color}")private String fontColor;@Value("${kaptcha.image.width}")private String imageWidth;@Value("${kaptcha.image.height}")private String imageHeight;@Value("${kaptcha.seesion.key}")private String sessionKey;@Value("${kaptcha.textproducer.char.length}")private String charLength;@Value("${kaptcha.textproducer.font.names}")private String fontNames;@Value("${kaptcha.textproducer.font.size}")private String fontSize;@Bean(name = "captchaProducer")public DefaultKaptcha getKaptchaBean(){DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();properties.setProperty("kaptcha.border", border);properties.setProperty("kaptcha.border.color", borderColor);properties.setProperty("kaptcha.textproducer.font.color", fontColor);properties.setProperty("kaptcha.image.width", imageWidth);properties.setProperty("kaptcha.image.height", imageHeight);properties.setProperty("kaptcha.seesion.key", sessionKey);properties.setProperty("kaptcha.textproducer.char.length", charLength);properties.setProperty("kaptcha.textproducer.font.names", fontNames);properties.setProperty("kaptcha.textproducer.font.size", fontSize);defaultKaptcha.setConfig(new Config(properties));return defaultKaptcha;}
}

验证码加载

CaptchaConroller

@RestController
public class CaptchaController {@AutowiredDefaultKaptcha captchaProducer;@RequestMapping(value = "/kaptcha", method = RequestMethod.GET)public void kaptcha(HttpSession session, HttpServletResponse response) throws IOException {//禁止使用缓存图片response.setDateHeader("Expires", 0);response.setHeader("Cache-Control", "no-store, no-cache, ,ust-revalidate");response.setHeader("Cache-Control", "post-check=0, pre-check=0");response.setHeader("Pragma", "no-cache");response.setContentType("image/jpeg");//创建验证码文本String text = captchaProducer.createText();//把文本存进缓存session.setAttribute(MyContants.CAPTCHA_CODE_KEY, new CaptchaImageVO(text, 2 * 60));try (ServletOutputStream outputStream = response.getOutputStream()) {//创建验证码图片BufferedImage image = captchaProducer.createImage(text);//把图片送到页面ImageIO.write(image,"jpg", outputStream);outputStream.flush();}}}

CaptchaImageVO(设置图片过期时间)

public class CaptchaImageVO {private String code;private LocalDateTime expireTime;//设置验证码过期时间public CaptchaImageVO(String code, int expireAfterSeconds){this.code = code;this.expireTime = LocalDateTime.now().plusSeconds(expireAfterSeconds);}public String getCode() {return code;}//判断验证码是否过期public boolean isExpired(){return LocalDateTime.now().isAfter(expireTime);}
}

login.html

<form action="/login" method="post"><span>用户名称</span><input type="text" name="username"><br><span>用户密码</span><input type="password" name="password"><br><span>验证码</span><input type="text" name="captchaCode" id="captchaCode"><img src="/kaptcha" id="kaptcha" width="110px" height="40px"><br><input type="submit" value="登录"><label><input type="checkbox" name="remember-me">记住密码</label>
</form>
<script>window.onload = function (ev) {var kaptchaImg = document.getElementById("kaptcha");kaptchaImg.onclick = function (ev1) {kaptchaImg.src = "/kaptcha?" + Math.floor(Math.random() * 100)}};
</script>

验证码校验

CaptchaCodeFilter

package cn.edu.lingnan.auth.imagecode;import cn.edu.lingnan.auth.MyAuthenticationFailureHandler;
import cn.edu.lingnan.utils.MyContants;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Objects;@Component
public class CaptchaCodeFilter extends OncePerRequestFilter {@AutowiredMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,FilterChain filterChain)throws ServletException, IOException {if(StringUtils.equals("/login", httpServletRequest.getRequestURI())&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")){//验证谜底与用户输入是否匹配try {//验证码谜底与用户输入是否匹配validate(new ServletWebRequest(httpServletRequest));}catch (AuthenticationException e){myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);return;}}//继续做后面的验证filterChain.doFilter(httpServletRequest,httpServletResponse);}private void validate(ServletWebRequest request) throws ServletRequestBindingException {HttpSession session = request.getRequest().getSession();String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "captchaCode");//判断验证码是否为空if (StringUtils.isEmpty(codeInRequest)) {throw new SessionAuthenticationException("验证码不能为空");}//获取服务器session池中的验证码CaptchaImageVO codeInsession = (CaptchaImageVO) session.getAttribute(MyContants.CAPTCHA_CODE_KEY);if (Objects.isNull(codeInsession)) {throw new SessionAuthenticationException("验证码不存在");}//校验服务器session池中的验证码是否过期if (codeInsession.isExpired()) {session.removeAttribute(MyContants.CAPTCHA_CODE_KEY);throw new SessionAuthenticationException("验证码已经过期");}//请求验证码校验if (!StringUtils.equals(codeInsession.getCode(), codeInRequest)) {throw new SessionAuthenticationException("验证码不匹配");}}
}

Mycontants(工具类)

public class MyContants {public static final String CAPTCHA_CODE_KEY = "captcha_key";
}

SecurityConfig(配置captcha)

@Autowired
private CaptchaCodeFilter captchaCodeFilter;http.addFilterBefore(captchaCodeFilter, UsernamePasswordAuthenticationFilter.class)

6、短息验证码验证

1.获取短信验证码

smscontroller

@Slf4j
@RestController
public class SmsController {@AutowiredMyUserDetailsMapper myUserDetailsMapper;@RequestMapping(value = "/smscode", method = RequestMethod.GET)public AJaxResponse sms(@RequestParam String mobile, HttpSession session){MyUserDetails myUserDetails = myUserDetailsMapper.findByUserName(mobile);if (myUserDetails == null) {return AJaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR,"你输入的手机未注册"));}SmsCode smsCode = new SmsCode(RandomStringUtils.randomNumeric(4), 60, mobile);//调用短信服务提供商接口log.info(smsCode.getCode() + "+>" + mobile);session.setAttribute(MyContants.SMS_CODE_KEY, smsCode);return AJaxResponse.success("短信验证码已经发送成功");}
}

smscode

public class SmsCode {private String code;//短信验证码private LocalDateTime expireTime;//过期时间private String moblie;//设置验证码过期时间public SmsCode(String code, int expireAfterSeconds, String moblie){this.code = code;this.expireTime = LocalDateTime.now().plusSeconds(expireAfterSeconds);this.moblie = moblie;}public String getCode() {return code;}public LocalDateTime getExpireTime() {return expireTime;}public String getMoblie() {return moblie;}//判断验证码是否过期public boolean isExpired(){return LocalDateTime.now().isAfter(expireTime);}
}

login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>首页</title><script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<form action="/login" method="post"><span>用户名称</span><input type="text" name="username"><br><span>用户密码</span><input type="password" name="password"><br><span>验证码</span><input type="text" name="captchaCode" id="captchaCode"><img src="/kaptcha" id="kaptcha" width="110px" height="40px"><br><input type="submit" onclick="login()" value="登录"><label><input type="checkbox" name="remember-me">记住密码</label>
</form><h1>短息登录</h1>
<form action="/smslogin" method="post"><span>手机号码:</span><input type="text" name="mobile" id="mobile"><br><span>短息验证码:</span><input type="text" name="smsCode" id="smsCode"><input type="button" onclick="getSmsCode()" value="获取"><br><input type="button" onclick="smslogin()" value="登录">
</form>
<script>window.onload = function (ev) {var kaptchaImg = document.getElementById("kaptcha");kaptchaImg.onclick = function (ev1) {kaptchaImg.src = "/kaptcha?" + Math.floor(Math.random() * 100)}};function login() {var username = $("#username").val();var password = $("#password").val();var captchaCode = $("#captchaCode").val();var rememberME = $("#remember-me").is(":checked");if (username === "" || password === ""){alert('用户名或密码不能为空');return;}$.ajax({type: "POST",url: "/login",data: {"username": username,"password": password,"captchaCode": captchaCode,"remember-me": rememberME},success: function (json) {if (json.isok) {location.href = json.data;} else {alert(json.message);location.href = '/login.html'}},error: function (e) {console.log(e.responseText)}})}function getSmsCode() {$.ajax({type: "get",url: "/smscode",data: {"mobile": $("#mobile").val()},success: function (json) {if (json.isok) {alert(json.data)} else {alert(json.message)}},error: function (e) {console.log(e.responseText)}})}function smslogin() {var mobile = $("#mobile").val();var smsCode = $("#smsCode").val();if(mobile === "" || smsCode === ""){alert('手机号和短息验证码不能为空');return ;}$.ajax({type: "POST",url: "/smslogin",data: {"mobile": mobile,"smsCode": smsCode},success: function (json) {if(json.isok){location.href = json.data;}else {alert(json.message)location.href = '/login.html'}},error: function (e) {console.log(e.responseText);}});}
</script>
</body>
</html>

2.短信验证

smsCodeVlidataFilter

@Component
public class SmsCodeValidataFilter extends OncePerRequestFilter {@AutowiredMyUserDetailsMapper myUserDetailsMapper;@AutowiredMyAuthenticationFailureHandler myAuthenticationFailureHandler;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,FilterChain filterChain)throws ServletException, IOException {if(StringUtils.equals("/smslogin", httpServletRequest.getRequestURI())&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")){//验证谜底与用户输入是否匹配try {//验证码谜底与用户输入是否匹配validate(new ServletWebRequest(httpServletRequest));}catch (AuthenticationException e){myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);return;}}//继续做后面的验证filterChain.doFilter(httpServletRequest,httpServletResponse);}private void validate(ServletWebRequest request) throws ServletRequestBindingException {HttpSession session = request.getRequest().getSession();SmsCode codeInSession = (SmsCode) session.getAttribute(MyContants.SMS_CODE_KEY);String mobileInRequest = request.getParameter("mobile");String codeInRequest = request.getParameter("smsCode");if (StringUtils.isEmpty(mobileInRequest)) {throw new SessionAuthenticationException("手机号码不能为空");}if (StringUtils.isEmpty(codeInRequest)) {throw new SessionAuthenticationException("短息验证码不能为空");}if (Objects.isNull(codeInSession)) {throw new SessionAuthenticationException("验证码不存在");}if (codeInSession.isExpired()) {session.removeAttribute(MyContants.SMS_CODE_KEY);throw new SessionAuthenticationException("验证码已经过期");}if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {throw new SessionAuthenticationException("验证码不匹配");}if (!codeInSession.getMoblie().equals(mobileInRequest)) {throw new SessionAuthenticationException("短息发送目标与你输入手机号不一致");}MyUserDetails myUserDetails = myUserDetailsMapper.findByUserName(mobileInRequest);if (Objects.isNull(myUserDetails)) {throw new SessionAuthenticationException("你输入的手机号不是系统的注册用户");}session.removeAttribute(MyContants.SMS_CODE_KEY);}
}

3.修改验证过滤器

smsCodeAuthenticationFiletr

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;private boolean postOnly = true;public SmsCodeAuthenticationFilter() {super(new AntPathRequestMatcher("/smslogin", "POST"));}public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response)throws AuthenticationException {if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {String mobile = this.obtainMobile(request);if (mobile == null) {mobile = "";}mobile = mobile.trim();SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}@Nullableprotected String obtainMobile(HttpServletRequest request) {return request.getParameter(this.mobileParameter);}protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}public void setMobileParameter(String mobileParameter) {Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");this.mobileParameter = mobileParameter;}public void setPostOnly(boolean postOnly) {this.postOnly = postOnly;}public final String getMobileParameter() {return this.mobileParameter;}}

SmsCodeAuthenticationToken

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = 520L;private final Object principal;public SmsCodeAuthenticationToken(Object principal) {super((Collection)null);this.principal = principal;this.setAuthenticated(false);}public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;super.setAuthenticated(true);}public Object getPrincipal() {return this.principal;}public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {if (isAuthenticated) {throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");} else {super.setAuthenticated(false);}}public void eraseCredentials() {super.eraseCredentials();}@Overridepublic Object getCredentials() {return null;}}

SmsCodeAuthenticationProvider

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {private UserDetailsService userDetailsService;public void setUserDetailsService(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}protected UserDetailsService getUserDetailsService() {return this.userDetailsService;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;UserDetails userDetails = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());if (userDetails == null){throw new InternalAuthenticationServiceException("无法根据手机号获取用户信息");}SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());authenticationResult.setDetails(authenticationToken.getDetails());return authenticationResult;}@Overridepublic boolean supports(Class<?> aClass) {return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);}
}

4.综合配置

SmsCodeSecurityConfig

@Component
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@AutowiredMyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@AutowiredMyAuthenticationFailureHandler myAuthenticationFailureHandler;@AutowiredMyUserDetailsServiceImpl myUserDetailsService;@AutowiredSmsCodeValidataFilter smsCodeValidataFilter;@Overridepublic void configure(HttpSecurity http) throws Exception {SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();smsCodeAuthenticationProvider.setUserDetailsService(myUserDetailsService);http.addFilterBefore(smsCodeValidataFilter, UsernamePasswordAuthenticationFilter.class);http.authenticationProvider(smsCodeAuthenticationProvider).addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}

SecurityConfig

@Autowired
private SmsCodeSecurityConfig smsCodeSecurityConfig;http.apply(smsCodeSecurityConfig)

这篇关于Srping Security学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/410933

相关文章

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06