赞
踩
如下图所示:
本文主要利用JWT实现登录授权,一旦登录之后,就返回给前端一个Token,利用Token进行信息交互。
Token由三部分组成:Header、Payload、Signature。
翻看源码可知,Spring Security通过职责链模式,通过各类XXXFilter对请求进行了拦截。实际操作时经常需要实现XXXFilter来自定义的登录以及访问控制。
/** * @author qyl * @program HelloController.java * @Description Test for diff auths * @createTime 2022-07-12 10:24 */ @RestController @RequestMapping("/test") public class TestController { @ApiOperation(value = "hello") @PostMapping("hello") @PreAuthorize("hasAuthority('hello')") public R hello() { return R.ok().data("hello", "hello"); } @ApiOperation(value = "manage") @PostMapping("manage") @PreAuthorize("hasAuthority('manage')") public R manage() { return R.ok().data("manage", "manage"); } }
/** * <p> * 访问过滤器 * 首先获取header中的token 判断是否已经登录; * 1.没登陆 抛异常 * 2.登录过了 就将授权信息一起放入安全上下文中 * </p> * * @author qyl * @since 2022-7-09 */ public class TokenAuthenticationFilter extends BasicAuthenticationFilter { private TokenManager tokenManager; private RedisTemplate redisTemplate; public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) { super(authManager); this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; } @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { UsernamePasswordAuthenticationToken authentication = null; authentication = getAuthentication(req); if (authentication != null) { SecurityContextHolder.getContext().setAuthentication(authentication); } else { ResponseUtil.out(res, R.error()); } chain.doFilter(req, res); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { // token置于header里 String token = request.getHeader("token"); if (token != null && !"".equals(token.trim())) { String userName = tokenManager.getUserFromToken(token); List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName); Collection<GrantedAuthority> authorities = new ArrayList<>(); for(String permissionValue : permissionValueList) { if(StringUtils.isEmpty(permissionValue)) continue; SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue); authorities.add(authority); } if (!StringUtils.isEmpty(userName)) { return new UsernamePasswordAuthenticationToken(userName, token, authorities); } return null; } return null; } }
/** * <p> * 登录过滤器,继承UsernamePasswordAuthenticationFilter, * 通过对Post发送的/login请求进行拦截,对用户名密码进行登录校验 * </p> * * @author qyl * @since 2022-7-09 */ public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; private TokenManager tokenManager; private RedisTemplate redisTemplate; public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) { this.authenticationManager = authenticationManager; this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; this.setPostOnly(false); this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { String username = req.getParameter("username"); String password = req.getParameter("password"); User user = new User(); user.setUsername(username); user.setPassword(password); return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>())); } /** * 登录成功将用户信息放入redis中: * username 作为 key * permissionValueList 作为 value * 之后将token返回以便在访问控制时从redis中取出权限,实现访问控制 * * @param req * @param res * @param chain * @param auth * @throws IOException * @throws ServletException */ @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { SecurityUser user = (SecurityUser) auth.getPrincipal(); String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername()); redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList()); ResponseUtil.out(res, R.ok().data("token", token)); } /** * 登录失败 * @param request * @param response * @param e * @throws IOException * @throws ServletException */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { ResponseUtil.out(response, R.error()); } }
/** * <p> * 自定义userDetailsService - 认证用户详情 * </p> */ @Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Autowired private PermissionService permissionService; /*** * 根据账号获取用户信息 * @param username: * @return: org.springframework.security.core.userdetails.UserDetails */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库中取出用户信息 User user = userService.selectByUsername(username); // 判断用户是否存在 if (null == user){ throw new UsernameNotFoundException("用户名不存在!"); } // 返回UserDetails实现类 User curUser = new User(); BeanUtils.copyProperties(user,curUser); List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId()); SecurityUser securityUser = new SecurityUser(curUser); securityUser.setPermissionValueList(authorities); return securityUser; }
/** * <p> * Security配置类 * </p> * * @author qyl * @since 2022-7-09 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService; private TokenManager tokenManager; private DefaultPasswordEncoder defaultPasswordEncoder; private RedisTemplate redisTemplate; @Autowired public SecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder, TokenManager tokenManager, RedisTemplate redisTemplate) { this.userDetailsService = userDetailsService; this.defaultPasswordEncoder = defaultPasswordEncoder; this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; } /** * 配置设置 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() // 如果有异常 直接就抛出 不再跳转到登录页面 .authenticationEntryPoint(new UnauthorizedEntryPoint()) .and().csrf().disable() //关闭csrf .authorizeRequests() .anyRequest().authenticated() // 对所有的请求都进行验证 .and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)) // 增加两个过滤器 职责链模式 .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic(); } /** * 密码处理 这里通过注入的userDetailsService来实现Form传来的账号密码与数据库账号密码进行比对 * * @param auth * @throws Exception */ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder); } /** * 配置哪些请求不拦截 * * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/v2/**", "/api/**"); } }
JWT需要注意细节:
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。