1.数据库配置好
2.导依赖
1 <!-- spring security 安全认证 --> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-security</artifactId> 5 </dependency> 6 7 <!--Token生成与解析--> 8 <dependency> 9 <groupId>io.jsonwebtoken</groupId> 10 <artifactId>jjwt</artifactId> 11 <version>${jwt.version}</version> 12 </dependency>
3.新建UserDetailsServiceImpl用户认证逻辑类实现UserDetailsService接口:
1package com.xr.blog.tools.security; 2 3import com.xr.blog.exception.CustomException; 4import com.xr.blog.pojo.SysUser; 5import com.xr.blog.service.SysPermissionService; 6import com.xr.blog.service.SysUserService; 7import com.xr.blog.tools.text.StringUtils; 8import org.slf4j.Logger; 9import org.slf4j.LoggerFactory; 10import org.springframework.beans.factory.annotation.Autowired; 11import org.springframework.security.core.userdetails.UserDetails; 12import org.springframework.security.core.userdetails.UserDetailsService; 13import org.springframework.security.core.userdetails.UsernameNotFoundException; 14import org.springframework.stereotype.Service; 15 16/** 17 * 用户验证处理 18 */ 19@Service 20public class UserDetailsServiceImpl implements UserDetailsService { 21 private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); 22 23 @Autowired 24 private SysUserService userService; 25 26 @Autowired 27 private SysPermissionService permissionService; 28 29 @Override 30 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 31 { 32 //此处通过用户输入得用户名去数据库查找用户 33 SysUser user = userService.findByUserName(username); 34 //账号验证 35 if (StringUtils.isNull(user)) { 36 log.info("登录用户:{} 不存在.", username); 37 throw new UsernameNotFoundException("登录用户:" + username + " 不存在"); 38 } 39 else if (UserStatus.DISABLE.getCode().equals(user.getIsDisable())) { 40 log.info("登录用户:{} 已被禁用.", username); 41 throw new CustomException("对不起,您的账号:" + username + " 已禁用"); 42 } 43 return createLoginUser(user); 44 } 45 46 47 //创建登录信息,其中loginuser实现了UserDetails接口 48 public UserDetails createLoginUser(SysUser user) { 49 return new LoginUser(user, permissionService.getMenuPermission(user.getId())); 50 } 51}
4.新建SecurityConfig类继承WebSecurityConfigurerAdapter:
1package com.xr.blog.tools.security; 2 3import org.springframework.beans.factory.annotation.Autowired; 4import org.springframework.context.annotation.Bean; 5import org.springframework.http.HttpMethod; 6import org.springframework.security.authentication.AuthenticationManager; 7import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11import org.springframework.security.config.http.SessionCreationPolicy; 12import org.springframework.security.core.userdetails.UserDetailsService; 13import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 15 16/** 17 * spring security配置 18 */ 19@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) 20public class SecurityConfig extends WebSecurityConfigurerAdapter 21{ 22 /** 23 * 自定义用户认证逻辑 24 */ 25 @Autowired 26 private UserDetailsService userDetailsService; 27 28 /** 29 * 认证失败处理类 30 */ 31 @Autowired 32 private AuthenticationEntryPointImpl unauthorizedHandler; 33 34 /** 35 * 退出处理类 36 */ 37 @Autowired 38 private LogoutSuccessHandlerImpl logoutSuccessHandler; 39 40 /** 41 * token认证过滤器 42 */ 43 @Autowired 44 private JwtAuthenticationTokenFilter authenticationTokenFilter; 45 46 /** 47 * 解决 无法直接注入 AuthenticationManager 48 * 49 * @return 50 * @throws Exception 51 */ 52 @Bean 53 @Override 54 public AuthenticationManager authenticationManagerBean() throws Exception 55 { 56 return super.authenticationManagerBean(); 57 } 58 59 /** 60 * anyRequest | 匹配所有请求路径 61 * access | SpringEl表达式结果为true时可以访问 62 * anonymous | 匿名可以访问 63 * denyAll | 用户不能访问 64 * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) 65 * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 66 * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 67 * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 68 * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 69 * hasRole | 如果有参数,参数表示角色,则其角色可以访问 70 * permitAll | 用户可以任意访问 71 * rememberMe | 允许通过remember-me登录的用户访问 72 * authenticated | 用户登录后可访问 73 */ 74 @Override 75 protected void configure(HttpSecurity httpSecurity) throws Exception { 76 httpSecurity 77 .cors() 78 .and() 79 // CRSF禁用,因为不使用session 80 .csrf().disable() 81 // 认证失败处理类(未登录直接请求资源返回未认证) 82 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() 83 // 基于token,所以不需要session 84 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() 85 // 过滤请求 86 .authorizeRequests() 87 // 对于登录login 验证码captchaImage 允许匿名访问 88 .antMatchers("/**/login", "/**/captchaImage","/").anonymous() 89 .antMatchers( 90 HttpMethod.GET, 91 "/*.html", 92 "/**/*.html", 93 "/**/*.css", 94 "/**/*.js", 95 "/*.ico", 96 "/*.txt" 97 ).permitAll() 98 .antMatchers("/vendor/**").anonymous() 99 .antMatchers("/images/**").anonymous() 100 .antMatchers("/css/**").anonymous() 101 .antMatchers("/js/**").anonymous() 102 .antMatchers("/webjars/**").anonymous() 103 .antMatchers("/druid/**").anonymous() 104 // 除上面外的所有请求全部需要鉴权认证 105 .anyRequest().authenticated() 106 .and() 107 .headers().frameOptions().disable(); 108 //添加登出成功处理类logoutSuccessHandler,此时logoutUrl将失效 109 110httpSecurity.logout().logoutUrl("/**/logout").logoutSuccessHandler(logoutSuccessHandler); 111 // 添加JWT filter 112 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); 113 } 114 115 116 /** 117 * 强散列哈希加密实现 118 */ 119 @Bean 120 public BCryptPasswordEncoder bCryptPasswordEncoder() 121 { 122 return new BCryptPasswordEncoder(); 123 } 124 125 /** 126 * 身份认证接口 127 */ 128 @Override 129 protected void configure(AuthenticationManagerBuilder auth) throws Exception 130 { 131 132 //设置自定义认证类,并设置密码加密规则 133 auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); 134 } 135}
5.认证失败处理类AuthenticationEntryPointImpl实现AuthenticationEntryPoint,此类中得逻辑就是处理失败后返回什么信息给客户端,自定义即可:
1package com.xr.blog.tools.security; 2 3import com.alibaba.fastjson.JSON; 4import com.xr.blog.tools.Result; 5import com.xr.blog.tools.ServletUtils; 6import com.xr.blog.tools.constant.Code; 7import com.xr.blog.tools.text.StringUtils; 8import org.springframework.security.core.AuthenticationException; 9import org.springframework.security.web.AuthenticationEntryPoint; 10import org.springframework.stereotype.Component; 11 12import javax.servlet.http.HttpServletRequest; 13import javax.servlet.http.HttpServletResponse; 14import java.io.IOException; 15import java.io.Serializable; 16 17/** 18 * 认证失败处理类 返回未授权 19 */ 20@Component 21public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { 22 private static final long serialVersionUID = -8970718410437077606L; 23 24 @Override 25 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) 26 throws IOException { 27 String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); 28 ServletUtils.renderString(response, JSON.toJSONString(Result.of(Code.SC_UNAUTHORIZED.getState(), msg,msg))); 29 } 30}
6.退出处理类LogoutSuccessHandlerImpl实现LogoutSuccessHandler,此类用于用户退出操作时实现得逻辑:
1package com.xr.blog.tools.security; 2 3import com.alibaba.fastjson.JSON; 4import com.xr.blog.tools.Result; 5import com.xr.blog.tools.ServletUtils; 6import com.xr.blog.tools.constant.Code; 7import com.xr.blog.tools.text.StringUtils; 8import org.springframework.beans.factory.annotation.Autowired; 9import org.springframework.context.annotation.Configuration; 10import org.springframework.security.core.Authentication; 11import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 12 13import javax.servlet.ServletException; 14import javax.servlet.http.HttpServletRequest; 15import javax.servlet.http.HttpServletResponse; 16import java.io.IOException; 17 18/** 19 * 自定义退出处理类 返回成功 20 */ 21@Configuration 22public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { 23 @Autowired 24 private TokenService tokenService; 25 26 /** 27 * 退出处理 28 */ 29 @Override 30 public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 31 throws IOException, ServletException { 32 LoginUser loginUser = tokenService.getLoginUser(request); 33 if (StringUtils.isNotNull(loginUser)) 34 { 35 //String userName = loginUser.getUsername(); 36 // 删除用户缓存记录 37 tokenService.delLoginUser(loginUser.getToken()); 38 // 记录用户退出日志 39 //AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Code.LOGOUT, "退出成功")); 40 } 41 ServletUtils.renderString(response, JSON.toJSONString(Result.of(Code.SC_OK.getState(),"退出成功", "退出成功"))); 42 } 43}
7.token认证过滤器JwtAuthenticationTokenFilter继承OncePerRequestFilter:
1package com.xr.blog.tools.security; 2 3import com.xr.blog.tools.text.StringUtils; 4import org.springframework.beans.factory.annotation.Autowired; 5import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6import org.springframework.security.core.context.SecurityContextHolder; 7import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 8import org.springframework.stereotype.Component; 9import org.springframework.web.filter.OncePerRequestFilter; 10 11import javax.servlet.FilterChain; 12import javax.servlet.ServletException; 13import javax.servlet.http.HttpServletRequest; 14import javax.servlet.http.HttpServletResponse; 15import java.io.IOException; 16 17/** 18 * token过滤器 验证token有效性 19 */ 20@Component 21public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { 22 @Autowired 23 private TokenService tokenService; 24 25 @Override 26 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 27 throws ServletException, IOException 28 { 29 LoginUser loginUser = tokenService.getLoginUser(request); 30 if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) 31 { 32 tokenService.verifyToken(loginUser); 33 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); 34 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 35 SecurityContextHolder.getContext().setAuthentication(authenticationToken); 36 } 37 chain.doFilter(request, response); 38 } 39}
8.写一个controller层得登录接口:
1package com.xr.blog.controller; 2 3import com.xr.blog.service.LoginService; 4import com.xr.blog.service.SysUserService; 5import com.xr.blog.tools.Result; 6import com.xr.blog.tools.constant.Code; 7import com.xr.blog.tools.security.LoginBody; 8import org.apache.commons.codec.DecoderException; 9import org.springframework.beans.factory.annotation.Autowired; 10import org.springframework.web.bind.annotation.*; 11 12import javax.servlet.http.HttpServletRequest; 13import java.security.NoSuchAlgorithmException; 14import java.security.spec.InvalidKeySpecException; 15import java.util.List; 16 17@RestController 18@RequestMapping("/squirrelblog") 19public class LoginController { 20 @Autowired 21 LoginService loginService; 22 @Autowired 23 SysUserService sysUserService; 24 25 @PostMapping(value = "/login",produces = "application/json;charset=UTF-8") 26 public Result<String> login(@RequestBody LoginBody loginBody, HttpServletRequest request) throws NoSuchAlgorithmException, InvalidKeySpecException, DecoderException { 27 return loginService.login(loginBody); 28 } 29}
9.LoginService 的login方法如下:
1package com.xr.blog.service.impl; 2 3import com.alibaba.fastjson.JSON; 4import com.xr.blog.exception.CustomException; 5import com.xr.blog.exception.UserPasswordNotMatchException; 6import com.xr.blog.service.LoginService; 7import com.xr.blog.tools.IdUtils; 8import com.xr.blog.tools.Result; 9import com.xr.blog.tools.constant.Code; 10import com.xr.blog.tools.redis.RedisCache; 11import com.xr.blog.tools.security.LoginBody; 12import com.xr.blog.tools.security.LoginUser; 13import com.xr.blog.tools.security.TokenService; 14import io.jsonwebtoken.Jwts; 15import io.jsonwebtoken.SignatureAlgorithm; 16import org.apache.commons.codec.DecoderException; 17import org.springframework.beans.factory.annotation.Autowired; 18import org.springframework.beans.factory.annotation.Value; 19import org.springframework.security.authentication.AuthenticationManager; 20import org.springframework.security.authentication.BadCredentialsException; 21import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 22import org.springframework.security.core.Authentication; 23import org.springframework.stereotype.Service; 24 25import javax.annotation.Resource; 26import java.security.NoSuchAlgorithmException; 27import java.security.spec.InvalidKeySpecException; 28import java.util.HashMap; 29import java.util.Map; 30 31@Service 32public class LoginServiceImpl implements LoginService { 33 @Autowired 34 private TokenService tokenService; 35 @Autowired 36 RedisCache redisCache; 37 @Resource 38 private AuthenticationManager authenticationManager; 39 // 令牌秘钥 40 @Value("${token.secret}") 41 private String secret; 42 43 @Override 44 public Result<String> login(LoginBody loginBody) throws NoSuchAlgorithmException, DecoderException, InvalidKeySpecException { 45 //校验验证码 46 String verifyKey = Code.CAPTCHA_CODE_KEY + loginBody.getUuid(); 47 String captcha = redisCache.getCacheObject(verifyKey); 48 redisCache.deleteObject(verifyKey); 49 if (captcha == null){ 50 return Result.of(Code.VAILDATE_ERROR.getState(),"验证码已过期","验证码已过期"); 51 } 52 if(!captcha.equalsIgnoreCase(loginBody.getCode())){ 53 return Result.of(Code.VAILDATE_ERROR.getState(),"验证码错误","验证码错误"); 54 } 55 56 // 用户验证 57 Authentication authentication = null; 58 try 59 { 60 // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername 61 authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginBody.getUsername(), loginBody.getPassword())); 62 } 63 catch (Exception e) 64 { 65 if (e instanceof BadCredentialsException) 66 { 67 throw new UserPasswordNotMatchException(); 68 } 69 else 70 { 71 throw new CustomException(e.getMessage()); 72 } 73 } 74 LoginUser loginUser = (LoginUser) authentication.getPrincipal(); 75 return Result.of(Code.SC_OK.getState(),Code.SC_OK.getDescription(),tokenService.createToken(loginUser)); 76 } 77}
10.接下来测试登录,未登录请求后台接口就好了。。