spring boot mybatis security + jwt登录认证

在传统的开发中,登录采取的都是基于session认证的方式,session认证,session是由服务器产生的,服务器将产生的sessionId发送给客户端,客户端在将sessionId保存到cookie中。当请求时候客户端每次都需要携带这个sessionId,服务器将之前发送的sessionId比较客户端发送的sessionId,如果一致就完成认证。由于服务器要保存session数据,所以压力就很大,并且cookie不安全容易被跨站攻击。
token是无状态认证,服务端不需要存token的数据,用户每次认证成功都会产生一串token,每次请求任何资源的时候客户端必须携带这串token,不再需要用户名和密码,这样扩展性更高,现在也是非常的流行的认证方式。

什么是jwt?下面是来自于jwt官网的解释

JSON Web令牌(JWT)是一种开放标准(RFC
7519),它定义了一种紧凑和独立的方式,用于在各方之间以JSON对象的形式安全地传输信息。可以验证和信任此信息,因为它是数字签名的。JWT可以使用秘密签名(使用HMAC算法),也可以使用RSA或ECDSA对公钥/私钥对进行签名。

什么时候使用jwt?

这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用的一种特性,因为它开销小,而且能够很容易地跨不同领域使用。信息交换:JSON
Web令牌是在各方之间安全地传输信息的一种好方法。因为JWT可以进行签名-例如,使用公钥/私钥对-所以您可以确保发件人是他们所称的发送者。此外,由于签名是使用报头和有效载荷计算的,您还可以验证内容没有被篡改。

jwt的认证流程
1.用户使用用户名密码请求服务器
2.服务器进行验证用户信息
3.服务器通过验证发送给用户一个token
4.客户端存储token,并在每次请求时附加这个token值
5.服务器验证token,并返回数据

jwt的构成
jwt由三部分构成header、payload、secred 构成
其中header、payload是被加密了

在前后端不分离的情况下用传统的session认证当然是没有什么的问题的,但是现在都提倡前后端分离,后端只给前端数据。那么就推荐使用token认证了,当然用session认证也是可以了,前后端分离中极力推荐使用token来认证授权。

如何在springboot 中优雅的使用jwt认证?非常的简单首先在您的项目中引如jwt和security框架的的相关依赖,如下图所示

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

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

引入之后就能使用jwt了。
1.在实体类中就是用户信息的表User 中要继承UserDetails接口实现这几个方法如下图所示

public class UserDetail implements UserDetails {
    private long id;
    private String username;
    private String password;
    private Role role;
    private Date lastPasswordResetDate;

    public UserDetail(long id, String username, Role role, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.role = role;
    }

    public UserDetail(String username, String password, Role role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }

    public UserDetail(long id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    //返回分配给用户的角色列表
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(role.getName()));
        return authorities;
    }

    public long getId() {
        return id;
    }

    @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 true;
    }
    public Date getLastPasswordResetDate() {
        return lastPasswordResetDate;
    }


    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setLastPasswordResetDate(Date lastPasswordResetDate) {
        this.lastPasswordResetDate = lastPasswordResetDate;
    }

还有角色表 role

@Data
@Builder
public class Role {
    private Long id;
    private String name;
}

role表是用的权限表 其中name是权限名称

保存用户token的表这个表不是数据库是临时的如下图:

@Data
@AllArgsConstructor
public class ResponseUserToken {
    private String token;
    private UserDetail userDetail;
}

实现我们的Dao,不管你使用的是mybatis的mapper还是hibernate的repository都是一样的的操作。下面是UserService中的方法

                /**
                 * 注册用户
                 * @param userDetail
                 * @return
                 */
                UserDetail register(UserDetail userDetail);
            
                /**
                 * 登陆
                 * @param username
                 * @param password
                 * @return
                 */
                ResponseUserToken login(String username, String password);
            
            
                /**
                 * 刷新Token
                 * @param oldToken
                 * @return
                 */
                ResponseUserToken refresh(String oldToken);
            
            
                /**
                 * 登出
                 * @param token
                 */
                void logout(String token);
            
            
                /**
                 * 根据Token获取用户信息
                 * @param token
                 * @return
                 */
                UserDetail getUserByToken(String token);
    
                // 详细的UserServiceImpl实现如下图所示:
    
		        private  AuthenticationManager authenticationManager;
		        private  UserDetailsService userDetailsService;
		        private  JwtUtils jwtTokenUtil;
		        @Autowired
		        private UserRepository userRepository;
		    
		        @Autowired
		        private AuthorityRepository authorityRepository;
		    
		        @Value("${scitc.jwt.header}")
		        private String tokenHead;
		    
     
    
           @Autowired
            public AuthServiceImpl(AuthenticationManager authenticationManager, @Qualifier("CustomUserDetailsService")
                    UserDetailsService userDetailsService, JwtUtils jwtTokenUtil, UserRepository authMapper) {
                this.authenticationManager = authenticationManager;
                this.userDetailsService = userDetailsService;
                this.jwtTokenUtil = jwtTokenUtil;
                this.userRepository = userRepository;
            }
        
            @Transactional
            @Override
            public User register(User user) {
                final String username = user.getUsername();
                if(userRepository.findByUsername(username)!=null) {
                    throw new UsernameNotFoundException("用户已存在:" + username );
                }
                BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
                user.setPassword(encoder.encode(user.getPassword()));
                user.setLastPasswordResetDate(new Date());
        
                user = userRepository.save(user);
                Authority role = new Authority();
                role.setId(user.getId());
                role.setUserId(user.getId());
                role.setName("ROLE_USER");
                role = authorityRepository.save(role);
                user.getRoles().add(role);
                user = userRepository.save(user);
                return user;
            }
        
            @Override
            public ResponseUserToken login(String username, String password) {
                //用户验证
                final Authentication authentication = authenticate(username, password);
                //存储认证信息
                SecurityContextHolder.getContext().setAuthentication(authentication);
                //生成token
                final User userDetail = (User) authentication.getPrincipal();
                final String token = jwtTokenUtil.generateAccessToken(userDetail);
                //存储token
                jwtTokenUtil.putToken(username, token);
                return new ResponseUserToken(token, userDetail);
            }
        
        
            @Override
            public void logout(String token) {
                token = token.substring(tokenHead.length());
                String userName = jwtTokenUtil.getUsernameFromToken(token);
                jwtTokenUtil.deleteToken(userName);
            }
        
            @Override
            public ResponseUserToken refresh(String oldToken) {
                String token = oldToken.substring(tokenHead.length());
                String username = jwtTokenUtil.getUsernameFromToken(token);
                User userDetail = (User) userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.canTokenBeRefreshed(token, userDetail.getLastPasswordResetDate())){
                    token =  jwtTokenUtil.refreshToken(token);
                    return new ResponseUserToken(token, userDetail);
                }
                return null;
            }
        
            @Override
            public User getUserByToken(String token) {
                token = token.substring(tokenHead.length());
        
                System.out.println("token:" + token);
                return jwtTokenUtil.getUserFromToken(token);
            }
        
            private Authentication authenticate(String username, String password) {
                try {
                    //该方法会去调用userDetailsService.loadUserByUsername()去验证用户名和密码,如果正确,则存储该用户名密码到“security 的 context中”
                    return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
                } catch (DisabledException | BadCredentialsException e) {
                    throw new BaseException("出错");
                }
            }
        }

其中的AuthenticationManager 代表的认证信息管理,UserDetailsServicespring security官方实现用户登录账号认证用户信息的,JwtUtils是封装jwt的相关操作,UserRepository是用的hibernate。
其中注解value中的配置信息是通过application.properties配置文件获取的如下是application.properties中的配置信息如下:
#jwt认证

    scitc.jwt.header=Authorization 
    scitc.jwt.secret=mySecret
    scitc.jwt.expiration=86400
    scitc.jwt.tokenHead=Bearer 

核心工具类jwtUtils如下:

    @Component
    public class JwtUtils {
        public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN";
    
        private static final String CLAIM_KEY_USER_ID = "user_id";
        private static final String CLAIM_KEY_AUTHORITIES = "scope";
    
        @Autowired
        private AuthorityRepository authorityRepository;
    
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    
        private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
    
        @Value("${scitc.jwt.secret}")
        private String secret;
    
        @Value("${scitc.jwt.expiration}")
        private Long access_token_expiration;
    
        @Value("${scitc.jwt.expiration}")
        private Long refresh_token_expiration;
    
        private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    
        public User getUserFromToken(String token) {
            User userDetail = getCurrentUser();;
            try {
                final Claims claims = getClaimsFromToken(token);
                Integer userId = getUserIdFromToken(token);
                String username = claims.getSubject();
                String roleName = claims.get(CLAIM_KEY_AUTHORITIES).toString();
                List<Authority> role = authorityRepository.findAuthoritiesByUserId(userId);
    
    
                userDetail = new User(userId, username,  role, "");
    
                logger.info("Claims:" + claims);
                logger.info("userId:" + userId);
                logger.info("username:" + username);
                logger.info("roleName:" + roleName);
                logger.info("role:" + role);
                logger.info("userDetail:" + userDetail);
    
            } catch (Exception e) {
                userDetail = null;
            }
            return userDetail;
        }
    
        public Integer getUserIdFromToken(String token) {
            Integer userId;
            try {
                final Claims claims = getClaimsFromToken(token);
                userId = (Integer) claims.get(((CLAIM_KEY_USER_ID)));
            } catch (Exception e) {
                userId = 0;
            }
            return userId;
        }
    
        public String getUsernameFromToken(String token) {
            String username;
            try {
                final Claims claims = getClaimsFromToken(token);
                username = claims.getSubject();
            } catch (Exception e) {
                username = null;
            }
            return username;
        }
    
        public Date getCreatedDateFromToken(String token) {
            Date created;
            try {
                final Claims claims = getClaimsFromToken(token);
                created = claims.getIssuedAt();
            } catch (Exception e) {
                created = null;
            }
            return created;
        }
    
        public String generateAccessToken(User userDetail) {
            Map<String, Object> claims = generateClaims(userDetail);
            claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
            return generateAccessToken(userDetail.getUsername(), claims);
        }
    
        public Date getExpirationDateFromToken(String token) {
            Date expiration;
            try {
                final Claims claims = getClaimsFromToken(token);
                expiration = claims.getExpiration();
            } catch (Exception e) {
                expiration = null;
            }
            return expiration;
        }
    
        public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
            final Date created = getCreatedDateFromToken(token);
            return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                    && (!isTokenExpired(token));
        }
    
        public String refreshToken(String token) {
            String refreshedToken;
            try {
                final Claims claims = getClaimsFromToken(token);
                refreshedToken = generateAccessToken(claims.getSubject(), claims);
            } catch (Exception e) {
                refreshedToken = null;
            }
            return refreshedToken;
        }
    
    
        public Boolean validateToken(String token, UserDetails userDetails) {
            User userDetail = (User) userDetails;
            final long userId = getUserIdFromToken(token);
            final String username = getUsernameFromToken(token);
    //        final Date created = getCreatedDateFromToken(token);
            return (userId == userDetail.getId()
                    && username.equals(userDetail.getUsername())
                    && !isTokenExpired(token)
    //                && !isCreatedBeforeLastPasswordReset(created, userDetail.getLastPasswordResetDate())
            );
        }
    
        public String generateRefreshToken(User userDetail) {
            Map<String, Object> claims = generateClaims(userDetail);
            // 只授于更新 token 的权限
            String roles[] = new String[]{JwtUtils.ROLE_REFRESH_TOKEN};
            claims.put(CLAIM_KEY_AUTHORITIES, JSONUtil.toJSON(roles));
            return generateRefreshToken(userDetail.getUsername(), claims);
        }
    
        public void putToken(String userName, String token) {
            tokenMap.put(userName, token);
        }
    
        public void deleteToken(String userName) {
            tokenMap.remove(userName);
        }
    
        public boolean containToken(String userName, String token) {
            if (userName != null && tokenMap.containsKey(userName) && tokenMap.get(userName).equals(token)) {
                return true;
            }
            return false;
        }
        private Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser()
                        .setSigningKey(secret)
                        .parseClaimsJws(token)
                        .getBody();
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
        private Date generateExpirationDate(long expiration) {
            return new Date(System.currentTimeMillis() + expiration * 1000);
        }
    
        private Boolean isTokenExpired(String token) {
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }
    
        private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
            return (lastPasswordReset != null && created.before(lastPasswordReset));
        }
    
        private Map<String, Object> generateClaims(User userDetail) {
            Map<String, Object> claims = new HashMap<>(16);
            claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
            return claims;
        }
    
        private String generateAccessToken(String subject, Map<String, Object> claims) {
            return generateToken(subject, claims, access_token_expiration);
        }
    
        private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
            List<String> list = new ArrayList<>();
            for (GrantedAuthority ga : authorities) {
                list.add(ga.getAuthority());
            }
            return list;
        }
    
    
        private String generateRefreshToken(String subject, Map<String, Object> claims) {
            return generateToken(subject, claims, refresh_token_expiration);
        }
    
    
    
        private String generateToken(String subject, Map<String, Object> claims, long expiration) {
            return Jwts.builder()
                    .setClaims(claims)
                    .setSubject(subject)
                    .setId(UUID.randomUUID().toString())
                    .setIssuedAt(new Date())
                    .setExpiration(generateExpirationDate(expiration))
                    .compressWith(CompressionCodecs.DEFLATE)
                    .signWith(SIGNATURE_ALGORITHM, secret)
                    .compact();
        }
    
    }

  CLAIM_KEY_USER_ID:代表的当前认证用户的id
  CLAIM_KEY_AUTHORITIES:代表认证的信息
  secret:是jwt构成之一 
  access_token_expiration:是jwt认证的期限和session一样有自己的过期时间
  refresh_token_expiration:刷新token的和access_token_expiration是一样的作用
private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
这句话的作用是保存token,我们知道token是由32位组成的。Map的key和value刚好满足需求。

下面讲解jwtUtils这个工具类的每个方法的作用:
1.

    public User getUserFromToken(String token) {
            try {
                final Claims claims = getClaimsFromToken(token);
                Integer userId = getUserIdFromToken(token);
                String username = claims.getSubject();
                String roleName = claims.get(CLAIM_KEY_AUTHORITIES).toString();
                List<Authority> role = authorityRepository.findAuthoritiesByUserId(userId);
            } catch (Exception e) {
                userDetail = null;
            }
            return userDetail;
        }

这个方法的作用是获取登录成功的token信息
Claims:是用来获取认证成功的用户信息
getUserIdFromToken:是 用来获取认证成功的用户id,单独的可以取值出来
claims.getSubject():是用来获取认证成功的用户名
claims.get(CLAIM_KEY_AUTHORITIES).toString():是用来获取认证的用户权限如:ROLE_USER,ROLE_ADMIN

public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

这个方法是用来获取认证成功用户的用户名信息


       public Date getCreatedDateFromToken(String token) {
                Date created;
                try {
                    final Claims claims = getClaimsFromToken(token);
                    created = claims.getIssuedAt();
                } catch (Exception e) {
                    created = null;
                }
                return created;
            }
  //这个方法是用来获取认证成功后构建token成功返回构建成功的token日期
    public String generateAccessToken(User userDetail) {
            Map<String, Object> claims = generateClaims(userDetail);
            claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
            return generateAccessToken(userDetail.getUsername(), claims);
        }
       //这个方法是是用来访问token的信息
       public Date getExpirationDateFromToken(String token) {
            Date expiration;
            try {
                final Claims claims = getClaimsFromToken(token);
                expiration = claims.getExpiration();
            } catch (Exception e) {
                expiration = null;
            }
            return expiration;
        }

这个方法的作用是用来获取构建成功用户的信息。

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
            final Date created = getCreatedDateFromToken(token);
            return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                    && (!isTokenExpired(token));
        }

这段代码的作用是用来判断token是否已经失效。

    public String refreshToken(String token) {
            String refreshedToken;
            try {
                final Claims claims = getClaimsFromToken(token);
                refreshedToken = generateAccessToken(claims.getSubject(), claims);
            } catch (Exception e) {
                refreshedToken = null;
            }
            return refreshedToken;
        }

这段代码的作用是是用来刷新新的token。

      public Boolean validateToken(String token, UserDetails userDetails) {
            User userDetail = (User) userDetails;
            final long userId = getUserIdFromToken(token);
            final String username = getUsernameFromToken(token);
    //        final Date created = getCreatedDateFromToken(token);
            return (userId == userDetail.getId()
                    && username.equals(userDetail.getUsername())
                    && !isTokenExpired(token)
    //                && !isCreatedBeforeLastPasswordReset(created, userDetail.getLastPasswordResetDate())
            );
        }

这段代码的作用是用来对token进行验证。

    public String generateRefreshToken(User userDetail) {
            Map<String, Object> claims = generateClaims(userDetail);
            // 只授于更新 token 的权限
            String roles[] = new String[]{JwtUtils.ROLE_REFRESH_TOKEN};
            claims.put(CLAIM_KEY_AUTHORITIES, JSONUtil.toJSON(roles));
            return generateRefreshToken(userDetail.getUsername(), claims);
        }

这段代码的作用是用于权限的刷新。

     public void putToken(String userName, String token) {
            tokenMap.put(userName, token);
        }

这段代码的作用是将当前用户认证后的token添加到map集合。

    public void deleteToken(String userName) {
        tokenMap.remove(userName);
    }

这段代码作用用于用户退出时候,移除token

     public boolean containToken(String userName, String token) {
            if (userName != null && tokenMap.containsKey(userName) && tokenMap.get(userName).equals(token)) {
                return true;
            }
            return false;
        }

这段代码的作用是map中是否包含当前用户的token

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

这段代码做用是获取claims的属性

    private Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

这段代码的作用是设置token的生命周期

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

这段代码作用是用来验证token是否失效

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

这段代码的作用是否用来验证最后一次登录的的时间

    private Map<String, Object> generateClaims(User userDetail) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
        return claims;
    }

这段代码的作用是来创建 Claims的属性也就是登录成功用户的属性

    private String generateAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, access_token_expiration);
    }

这段代码是用于用户每次登录时候产生新的token


    private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
        List<String> list = new ArrayList<>();
        for (GrantedAuthority ga : authorities) {
            list.add(ga.getAuthority());
        }
        return list;
    }

这段代码的作用获取登录用户的角色权限

    private String generateRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, refresh_token_expiration);
    }

这段代码的作用是刷新每次登录的的token


    private String generateToken(String subject, Map<String, Object> claims, long expiration) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

}

这段代码的作用是用来创建新的token。

下面是WebSecurityConfig是配置信息如下:

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        private final JwtAuthenticationEntryPoint unauthorizedHandler;
    
        private final AccessDeniedHandler accessDeniedHandler;
    
        private final UserDetailsService CustomUserDetailsService;
    
        private final JwtAuthenticationTokenFilter authenticationTokenFilter;
    
        @Autowired
        public WebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler,
                                 @Qualifier("RestAuthenticationAccessDeniedHandler") AccessDeniedHandler accessDeniedHandler,
                                 @Qualifier("CustomUserDetailsService") UserDetailsService CustomUserDetailsService,
                                 JwtAuthenticationTokenFilter authenticationTokenFilter) {
            this.unauthorizedHandler = unauthorizedHandler;
            this.accessDeniedHandler = accessDeniedHandler;
            this.CustomUserDetailsService = CustomUserDetailsService;
            this.authenticationTokenFilter = authenticationTokenFilter;
        }
    
        @Autowired
        public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
            authenticationManagerBuilder
                    // 设置UserDetailsService
                    .userDetailsService(this.CustomUserDetailsService)
                    // 使用BCrypt进行密码的hash
                    .passwordEncoder(passwordEncoder());
        }
    
        /**
         * 装载BCrypt密码编码器
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
                    // 由于使用的是JWT,我们这里不需要csrf
                    .csrf().disable()
                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    // 基于token,所以不需要session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    
                    .authorizeRequests()
    
                    // 对于获取token的rest api要允许匿名访问
                    .antMatchers("/api/v1/login", "/api/v1/sign", "/error/**").permitAll()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
    
            // 禁用缓存
            httpSecurity.headers().cacheControl();
    
            // 添加JWT filter
            httpSecurity
                    .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        }
    
        @Override
        public void configure(WebSecurity web) {
            web
                    .ignoring()
                    .antMatchers(
                            "swagger-ui.html",
                            "**/swagger-ui.html",
                            "/favicon.ico",
                            "/**/*.css",
                            "/**/*.js",
                            "/**/*.png",
                            "/**/*.gif",
                            "/swagger-resources/**",
                            "/v2/**",
                            "/**/*.ttf"
                    );
            web.ignoring().antMatchers("/v2/api-docs",
                    "/swagger-resources/configuration/ui",
                    "/swagger-resources",
                    "/swagger-resources/configuration/security",
                    "/swagger-ui.html"
            );
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }

其中注意的是采用token认证,已经不需要session了。

下面来配置失败切入点:JwtAuthenticationEntryPoint

    @Component
    public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
    
        private static final long serialVersionUID = -8970718410437077606L;
    
        @Override
        public void commence(HttpServletRequest request,
                             HttpServletResponse response,
                             AuthenticationException authException) throws IOException {
            //验证为未登陆状态会进入此方法,认证错误
            System.out.println("认证失败:" + authException.getMessage());
            response.setStatus(200);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter printWriter = response.getWriter();
            String body = ResultJson.failure(ResultCode.UNAUTHORIZED, authException.getMessage()).toString();
            printWriter.write(body);
            printWriter.flush();
        }
    }

当认证进入此方法的时候说明登录失败。

下面是一个获取认证信息的tokenfilter:

    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Value("${jwt.header}")
        private String token_header;
    
        @Resource
        private JwtUtils jwtUtils;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
            String auth_token = request.getHeader(this.token_header);
            final String auth_token_start = "Bearer ";
            if (StringUtils.isNotEmpty(auth_token) && auth_token.startsWith(auth_token_start)) {
                auth_token = auth_token.substring(auth_token_start.length());
            } else {
                // 不按规范,不允许通过验证
                auth_token = null;
            }
    
            String username = jwtUtils.getUsernameFromToken(auth_token);
    
            logger.info(String.format("Checking authentication for userDetail %s.", username));
    
            if (jwtUtils.containToken(username, auth_token) && username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetail userDetail = jwtUtils.getUserFromToken(auth_token);
                if (jwtUtils.validateToken(auth_token, userDetail)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    logger.info(String.format("Authenticated userDetail %s, setting security context", username));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
            chain.doFilter(request, response);
        }

当权限不足时候执行如下方法:

    @Component("RestAuthenticationAccessDeniedHandler")
    public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
            //登陆状态下,权限不足执行该方法
            System.out.println("权限不足:" + e.getMessage());
            response.setStatus(200);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter printWriter = response.getWriter();
            String body = ResultJson.failure(ResultCode.FORBIDDEN, e.getMessage()).toString();
            printWriter.write(body);
            printWriter.flush();
        }

下面是数据库表结构:
在这里插入图片描述
其中sys_user是用户表,sys_role是角色表 sys_user_role是是鉴权表。

最后是控制器controller代码如下:

    @RestController
    @Api(description = "登陆注册及刷新token")
    @RequestMapping("/api/v1")
    public class AuthController {
        @Value("${jwt.header}")
        private String tokenHeader;
    
        private final AuthService authService;
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        public AuthController(AuthService authService) {
            this.authService = authService;
        }
    
        @PostMapping(value = "/login")
        @ApiOperation(value = "登陆", notes = "登陆成功返回token,测试管理员账号:admin,123456;用户账号:les123,admin")
        public ResultJson<ResponseUserToken> login(
                @Valid @RequestBody User user){
            final ResponseUserToken response = authService.login(user.getName(), user.getPassword());
            return ResultJson.ok(response);
        }
    
        @GetMapping(value = "/logout")
        @ApiOperation(value = "登出", notes = "退出登陆")
        @ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
        public ResultJson logout(HttpServletRequest request){
            String token = request.getHeader(tokenHeader);
            if (token == null) {
                return ResultJson.failure(ResultCode.UNAUTHORIZED);
            }
            authService.logout(token);
            return ResultJson.ok();
        }
    
        @RequestMapping(value = "/user",method = {RequestMethod.POST,RequestMethod.GET})
        @ApiOperation(value = "根据token获取用户信息", notes = "根据token获取用户信息")
        @ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
        public ResultJson getUser(HttpServletRequest request){
            String token = request.getHeader(tokenHeader);
            if (token == null) {
                return ResultJson.failure(ResultCode.UNAUTHORIZED);
            }
            UserDetail userDetail = authService.getUserByToken(token);
            logger.info("userDetail:" + userDetail);
            return ResultJson.ok(userDetail);
        }
        @PostMapping(value = "/sign")
        @ApiOperation(value = "用户注册")
        public ResultJson sign(@RequestBody User user) {
            if (StringUtils.isAnyBlank(user.getName(), user.getPassword())) {
                return ResultJson.failure(ResultCode.BAD_REQUEST);
            }
            UserDetail userDetail = new UserDetail(user.getName(), user.getPassword(), Role.builder().id(1l).build());
            return ResultJson.ok(authService.register(userDetail));
        }
        @GetMapping(value = "refresh")
        @ApiOperation(value = "刷新token")
        public ResultJson refreshAndGetAuthenticationToken(
                HttpServletRequest request){
            String token = request.getHeader(tokenHeader);
            ResponseUserToken response = authService.refresh(token);
            System.out.println("response:" + response);
            if(response == null) {
                return ResultJson.failure(ResultCode.BAD_REQUEST, "token无效");
            } else {
                return ResultJson.ok(response);
            }
        }

该demogithub地址如下:
https://github.com/zhoubiao188/springboot_mybatis_security_token

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值