第五次课:后台整合springsecurity

飞一样的编程
飞一样的编程
擅长邻域:Java,MySQL,Linux,nginx,springboot,mongodb,微信小程序,vue

分类: springboot 专栏: 在线教育项目实战 标签: security整合

2023-05-08 12:26:41 632浏览

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展开评论

周杰伦 能力:10

2023-05-15 16:42:06

我们一起加油 兄弟
蓝色妖姬 能力:10

2023-05-15 10:44:09

太累了,太多了,时间太紧了,要废了,太自卑了,加油吧
点击查看更多评论

展开评论

您可能感兴趣的博客

客服QQ 1913284695