本文主要是介绍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学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!