第五次课:后台整合springsecurity
分类: springboot 专栏: 在线教育项目实战 标签: security整合
2023-05-08 12:26:41 854浏览
security整合
依赖
<!--JWT 依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <!--spring security依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
配置类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource UrlSecurityMetadataSource urlSecurityMetadataSource; @Resource UserAccessDecisionManager userAccessDecisionManager; @Resource RestAuthenticationEntryPoint restAuthorizationEntryPoint; @Resource RestfulAccessDeniedHandler restfulAccessDeniedHandler; @Resource EduAclUserServiceImpl userService; //直接不放弃md5加盐加密 改成 @Bean BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 认证 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); } /** * 放行白名单 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/user/login", "/logout", "/css/**", "/js/**", "/index.html", "favicon.ico", "/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs/**", "/captcha", "/ws/**" ); } /** * 授权 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //使用JWT,不需要csrf http.csrf().disable() //基于token,不需要session .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //所有请求都要求认证 .and() //支持跨域访问 .cors() .and() .authorizeRequests() // 放行OPTIONS请求 // .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .anyRequest() .authenticated() //动态权限配置 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { //查找访问当前url需要什么角色(权限) object.setSecurityMetadataSource(urlSecurityMetadataSource); //判断(裁决)当前用户有没有这个角色(权限) object.setAccessDecisionManager(userAccessDecisionManager); return object; } }) .and() //禁用缓存 .headers() .cacheControl(); //添加jwt 登录授权过滤器 http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class); //添加自定义未授权和未登录结果返回 http.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthorizationEntryPoint); } @Bean public JwtAuthencationTokenFilter jwtAuthencationTokenFilter(){ return new JwtAuthencationTokenFilter(); } }
用户实体类改造
public class EduAclUser implements Serializable , UserDetails @TableField(exist = false) private List<EduAclRole> roles; @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (EduAclRole role : roles) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleCode()); authorities.add(authority); } return authorities; }
用户service改造
@Service public class EduAclUserServiceImpl extends ServiceImpl<EduAclUserMapper, EduAclUser> implements EduAclUserService, UserDetailsService { @Resource EduAclUserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询的时候一定要把密码和角色权限带上(所以要写sql映射文件) EduAclUser eduAclUser = userMapper.getByUsername(username); if (eduAclUser == null ){ throw new UsernameNotFoundException("用户不存在"); } return eduAclUser; }
菜单实体改造
@TableField(exist = false) private List<EduAclRole> roles;
菜单service改造
@Override public List<EduAclPermission> getAll() { //这个要写sql映射文件,查菜单的时候把角色带回来 return permissionMapper.getAll(); }
登录controller
@RestController @CrossOrigin public class LoginController { @Resource EduAclUserServiceImpl eduAclUserService; @Resource private PasswordEncoder passwordEncoder; @Resource private JwtTokenUtil jwtTokenUtil; @PostMapping("/user/login") public ResultDto login(@RequestBody AclUserVo userVo){ Map<String,String> resultMap = new HashMap<>(); String username = userVo.getUsername(); String password = userVo.getPassword(); UserDetails user = eduAclUserService.loadUserByUsername(username); if (null==user||!passwordEncoder.matches(password,user.getPassword())){ return ResultDto.error("用户名或密码不正确"); } if (!user.isEnabled()){ return ResultDto.error("账号被禁用,请联系管理员!"); } /** * 用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。 * spring security 提供会话管理, * 认证通过后将身份信息放入SecurityContextHolder上下文 */ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password,user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(token); //生成token String tokenStr = jwtTokenUtil.getToken(username); resultMap.put("token",tokenStr); return ResultDto.success("登录成功",resultMap); } }
查找访问当前url需要什么角色
@Component public class UrlSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Resource EduAclPermissionService permissionService; AntPathMatcher matcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) object).getRequestUrl(); List<EduAclPermission> allMenus = permissionService.getAll(); for (EduAclPermission menu : allMenus) { if(matcher.match(menu.getPermissionValue(),requestUrl)){ List<EduAclRole> roles = menu.getRoles(); String[] roleCodes = new String[roles.size()]; for (int i = 0; i < roles.size(); i++) { roleCodes[i] = roles.get(i).getRoleCode(); } return SecurityConfig.createList(roleCodes); } } return SecurityConfig.createList("ROLE_LOGIN"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return false; }
裁决当前用户有没有这个角色
@Component public class UserAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { ////判断用户角色是否为url所需角色 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (ConfigAttribute configAttribute : configAttributes) { //当前url所需角色 String needRole = configAttribute.getAttribute(); //判断角色是否登录即可访问的角色,此角色在CustomFilter中设置 if ("ROLE_LOGIN".equals(needRole)){ //判断是否登录 if (authentication instanceof AnonymousAuthenticationToken){ throw new AccessDeniedException("尚未登录,请登录!"); }else { return; } } for (GrantedAuthority authority : authorities) { if(needRole.equals(authority.getAuthority())){ return; } } } throw new AccessDeniedException("权限不足"); } @Override public boolean supports(ConfigAttribute attribute) { return false; } @Override public boolean supports(Class<?> clazz) { return false; } }
登录授权过滤器
public class JwtAuthencationTokenFilter extends OncePerRequestFilter { @Autowired private EduAclUserServiceImpl userInfoServie; @Autowired JwtTokenUtil jwtTokenUtil; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authToken = request.getHeader(tokenHeader); //存在token if (null != authToken && authToken.startsWith(tokenHead)) { String token = authToken.substring(tokenHead.length()); String username = jwtTokenUtil.getUserNameFromToken(token); UserDetails userDetails = userInfoServie.loadUserByUsername(username); //验证token是否有效,重新设置用户对象 if (jwtTokenUtil.validateToken(token)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } filterChain.doFilter(request, response); } }
退出登录
@ApiOperation(value = "退出登录") @PostMapping("/logout") public ResultDto logout(HttpServletRequest request, HttpServletResponse response){ //将项目中的用户信息清空 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(authentication !=null){ new SecurityContextLogoutHandler().logout(request,response,authentication); } response.setContentType("application/json;chartset=utf-8"); //请求头里的jwt令牌置空 response.setHeader("Authorization",""); return ResultDto.success("注销成功",null); }
jwt配置
#jwt存储的请求头 jwt.tokenHeader=Authorization #jwt加密使用的密钥 jwt.secret=jfit-secret #jwt的过期时间(60*60*24) jwt.expiration=604800 #jwt载荷中拿到开头 jwt.tokenHead=Bearer
jwt工具类
@Component public class JwtTokenUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String getToken(String username){ Map<String,Object> map = new HashMap<>(); map.put("typ","JWT"); return Jwts.builder().setHeaderParams(map) .setSubject(username) .setExpiration(new Date(System.currentTimeMillis()+expiration)) .signWith(SignatureAlgorithm.HS256,secret).compact(); } /** * 校验token的合法性 * @return */ public Boolean validateToken(String token ){ try { Claims body = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); return true; } catch (ExpiredJwtException e) { e.printStackTrace(); throw new JfException(50000,"token过期"); } catch (UnsupportedJwtException e) { e.printStackTrace(); return false; } catch (MalformedJwtException e) { e.printStackTrace(); return false; } catch (SignatureException e) { e.printStackTrace(); throw new JfException(50000,"验签失败"); } catch (IllegalArgumentException e) { e.printStackTrace(); return false; } } /** * 根据token拿到用户名 * @param token * @return */ public String getUserNameFromToken(String token) { Claims body = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); return body.getSubject(); } }
自定义未授权
@Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); PrintWriter out = response.getWriter(); ResultDto bean = ResultDto.error("权限不足,请联系管理员!"); bean.setCode(403); out.write(new ObjectMapper().writeValueAsString(bean)); out.flush(); out.close(); } }
自定义未登录
@Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); PrintWriter out = response.getWriter(); ResultDto bean = ResultDto.error("尚未登录,请登录!或者token令牌无效"); bean.setCode(401); out.write(new ObjectMapper().writeValueAsString(bean)); out.flush(); out.close(); } }
表数据
注意要跟自己前端vue写的对应起来,还有那个权限值要跟咱们写的controller接口路径对应上
好博客就要一起分享哦!分享海报
此处可发布评论
评论(2)展开评论
您可能感兴趣的博客
他的专栏
他感兴趣的技术