Spring Security+JWT认证和授权(学习记录)

1.开始

因为最近在做第二课堂项目遇到了一个问题,已经登陆的用户虽然没有对其开放管理员界面但是只要他用token请求接口还是会响应结果,没有达到权限管理的要求。于是我学习了Spring Security用它配合JWT达到认证和授权的目的。

主要依赖

 <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--jjwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

2. 创建security可识别的角色类

public class RoleUser implements UserDetails {
    String password; //密码
    String username;  //用户名
    boolean accountNonExpired;   //是否没过期
    boolean accountNonLocked;   //是否没被锁定
    boolean credentialsNonExpired;  //是否没过期
    boolean enabled;  //账号是否可用
    Collection<? extends GrantedAuthority> authorities;  //用户的权限集合

    public RoleUser(){}

    public RoleUser(String password, String username, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled, Collection<? extends GrantedAuthority> authorities) {
        this.password = password;
        this.username = username;
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
        this.enabled = enabled;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    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 setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
}

3.生成可识别对象

public class UserRoleServiceImpl implements UserRoleService {
    @Resource
    private UserMapper userMapper;

    private final String ROLE_ADMIN="admin";
    private final String ROLE_USER="user";


    @Override
    public UserDetails loadUserByUsername(String uid) throws UsernameNotFoundException {
        User u = userMapper.selectByPrimaryKey(Integer.parseInt(uid));
        System.out.println(u);
        RoleUser roleUser=null;
        if(ROLE_ADMIN.equals(u.getuLevel())){
            roleUser= new RoleUser(u.getuPass(),uid , true, true, true, true,
                    AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
        }
        if (ROLE_USER.equals(u.getuLevel())){
            roleUser= new RoleUser(u.getuPass(),uid , true, true, true, true,
                    AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
        }
        return roleUser;
    }
}

4.配置 SecurityConfig

@Configuration
@EnableWebSecurity //启动security过滤器链
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserRoleServiceImpl userRoleServiceImpl;

    @Resource
    private DataSource datasource;
    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    private static String[] WHITE_LIST={};

    /**
     *密码加密
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    /**
     *从数据库加载
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(userRoleServiceImpl).passwordEncoder(new BCryptPasswordEncoder());
    }

    /**
     *静态资源
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**","/fileUpload/**");
    }

    /**
     *继承权限
     */
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        return hierarchy;
    }


    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(datasource);
        //tokenRepository.setCreateTableOnStartup(true); // 第一次使用此功能自动创建表,第二次要关闭,否则报错
        return tokenRepository;
    }

    /**
     *数据库中需要添加前缀ROLE_,hasAnyRole可以识别前缀后的内容
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers(WHITE_LIST).permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()//authenticated()要求登录
                // 不需要session,开启session就直接使用JSESSIONID操作了
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.rememberMe()
                .rememberMeParameter("remember-me-new")
                .rememberMeCookieName("remember-me-cookie")
                .tokenValiditySeconds(2 * 24 * 60 * 60)//2.tokenRepository(persistentTokenRepository())
                .and().csrf().disable();

    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


}

5.jwt工具类

@Data
@Component
public class JwtTokenUtil {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Value("${jwt.header}")
    private String header;
    //建议更换时间
    private final Integer ADVANCE_EXPIRE_TIME=60000;


    /**
     * 生成token令牌
     *
     * @param username 用户
     * @return 令token牌
     */
    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub",username);
        claims.put("created", new Date());

        return generateToken(claims);
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     *
     * @param token       令牌
     * @param userDetails 用户
     * @return 是否有效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {

        String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }


    /**
     * 从claims生成令牌,如果看不懂就看谁调用它
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder().setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从令牌中获取数据声明,如果看不懂就看谁调用它
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }


    /**
     * 检查当前token是否还能继续使用
     * true:可以  false:不建议
     * @param token
     * @return
     */
    public boolean checkToken(String token){
        try {
            // jwt正常情况 则判断失效时间是否大于5分钟
            long expireTime = Jwts.parser()   //得到DefaultJwtParser
                    .setSigningKey(secret)  //设置签名的秘钥
                    .parseClaimsJws(token.replace("jwt_", ""))
                    .getBody().getExpiration().getTime();
            long diff = expireTime - System.currentTimeMillis();
            System.out.println(diff);
            //如果有效期小于5分钟,则不建议继续使用该token
            if (diff < ADVANCE_EXPIRE_TIME) {
                return false;
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

}

6.登录认证换取JWT令牌

    public String login(String username,String password) throws CustomException {
        try {
            UsernamePasswordAuthenticationToken upToken =
                    new UsernamePasswordAuthenticationToken(username, password);
            Authentication authentication = authenticationManager.authenticate(upToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }catch (AuthenticationException e){
            throw new CustomException(CustomExceptionType.USER_INPUT_ERROR
                    ,"用户名或者密码不正确");
        }

       // UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return jwtTokenUtil.generateToken(username);
    }

7.JwtAuthenticationTokenFilter过滤

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    JwtTokenUtil jwtTokenUtil;

    @Resource
    private UserRoleServiceImpl userRoleServiceImpl;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String jwtToken = request.getHeader(jwtTokenUtil.getHeader());

        if(!StringUtils.isEmpty(jwtToken)){
            //校验合法性并提取用户名
            String username = jwtTokenUtil.getUsernameFromToken(jwtToken);

            //合法,且未验证过
            if(username != null &&
                    SecurityContextHolder.getContext().getAuthentication() == null){

                UserDetails userDetails = userRoleServiceImpl.loadUserByUsername(username);
                //是否过期
                if(jwtTokenUtil.validateToken(jwtToken,userDetails)){
                    if (!jwtTokenUtil.checkToken(jwtToken)){
                        //更换令牌
                        response.setHeader("Access-Control-Expose-Headers",jwtTokenUtil.getHeader());
                        response.setHeader(jwtTokenUtil.getHeader(),jwtTokenUtil.refreshToken(jwtToken) );
                    }
                    //给使用该JWT令牌的用户进行授权
                    UsernamePasswordAuthenticationToken authenticationToken
                            = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    //交给Security管理
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }

        }
        //过滤器链继续走下去
        filterChain.doFilter(request,response);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring SecurityJWT(JSON Web Token)是一种常见的组合,用于实现身份验证和授权机制。 JWT是一种轻量级的身份验证和授权的解决方案,它使用JSON格式来定义安全声明。JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。头部包含算法和令牌类型等信息,载荷包含用户身份和其他相关信息,签名用于验证令牌的完整性和真实性。 Spring Security提供了对JWT的支持,并可以与Spring Boot框架无缝集成。您可以通过以下步骤来实现Spring Security + JWT的集成: 1. 添加依赖:在项目的构建文件中,添加Spring SecurityJWT相关的依赖,如spring-boot-starter-security和jjwt。 2. 配置Spring Security:创建一个继承自WebSecurityConfigurerAdapter的配置类,并重写configure方法来配置Spring Security。在该方法中,您可以定义身份验证和授权规则。 3. 创建JWT工具类:创建一个JWT工具类,用于生成、解析和验证JWT。您可以使用jjwt库来处理JWT操作。 4. 实现用户认证:在Spring Security配置类中,您可以实现UserDetailsService接口,并重写loadUserByUsername方法来根据用户名加载用户信息。在该方法中,您可以从数据库或其他数据源中获取用户信息,并构建一个UserDetails对象返回。 5. 实现JWT过滤器:创建一个自定义的过滤器,用于解析和验证传入请求中的JWT。在该过滤器中,您可以使用JWT工具类来解析JWT,并将用户信息添加到Spring Security的上下文中。 6. 配置Spring Security过滤器链:在Spring Security配置类中,将JWT过滤器添加到过滤器链中,以确保每个请求都经过JWT验证。 通过以上步骤,您可以实现Spring SecurityJWT的集成,实现基于JWT的身份验证和授权机制。这样,您可以使用JWT生成和验证令牌,并通过Spring Security保护您的应用程序资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值