用户登录及鉴权

  6 分钟   21803 字    |    

基于SpringBoot Security 与 JWT 的用户登录及鉴权

1 JWT 工具类

public class JwtTokenUtils {
    /**
     * 用户请求头中的Token的key(例:Authorization: *************)
     */
    public static final String TOKEN_HEADER = "Authorization";
    /**
     * 返回的Token前缀信息(自定义配置)
     */
    public static final String TOKEN_PREFIX = "Bearer ";
    /**
     * JWT的加密算法SigningKey
     */
    private static final String SECRET = "jwtSecret";
    /**
     * 该JWT的签发者,是否使用是可选的(可以使用项目名称或者作者名称之类)
     */
    private static final String ISS = "DEC";
    /**
     * 角色的key
     */
    private static final String ROLE_CLAIMS = "rol";
    /**
     * 过期时间是7天
     */
    private static final long EXPIRATION = 604800L;
//    private static final long EXPIRATION = 5*60;


    /**
     * 创建token方法
     * @param username 用户名
     * @param role 角色权限集合
     * @param
     * @return
     */
    public static String createToken(String username, List<String> role) {
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
                .compact();
    }

    /**
     * 从token中获取用户名
     * @param token
     * @return
     */
    public static String getUserName(String token){
        return getTokenBody(token).getSubject();
    }

    /**
     * 从token中获取用户角色集合
     * @param token
     * @return
     */
    public static List<String> getUserRole(String token){
        return (List<String>) getTokenBody(token).get(ROLE_CLAIMS);
    }

    /**
     * 判断是否已过期
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) {
        try {
            return getTokenBody(token).getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }

    /**
     * 获取token请求体内容
     * @param token
     * @return
     */
    private static Claims getTokenBody(String token){
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    }
}

2 数据层

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(type= IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private Set<String> roles;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", roles=" + roles +
                '}';
    }
}
//DTO
public class JwtUser implements UserDetails {
    /**
     * 用户ID
     */
    private Long id;
    /**
     * 用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String passWord;
    /**
     * 权限信息
     */
    private Collection<? extends GrantedAuthority> authorities;

    /**
     * 使用user创建jwtUser的构造器
     * @param user 用户对象
     */
    public JwtUser(User user) {
        id = user.getId();
        userName = user.getUsername();
        passWord = user.getPassword();
        if(user.getRoles() != null && user.getRoles().size() > 0){
            List<GrantedAuthority> list = new ArrayList<>();
            for (String role : user.getRoles()) {
                list.add(new SimpleGrantedAuthority(role));
            }
            authorities = list;
        }
    }

    @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 true;
    }

    @Override
    public String toString() {
        return "JwtUser{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", authorities=" + authorities +
                '}';
    }
}

UserDetailsService

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserService userService;
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        User user = userService.getUserByUsername(name);
        Set<String> roles = userMapper.selectRolesByUsername(name);
        user.setRoles(roles);
        return new JwtUser(user);
    }
}

Mapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
    @Select("select COUNT(*) from user")
    Integer listNumUser();

    @Select("select * from user where username = #{username}")
    User selectByUsername(String username);

    @Select("select roles from user where username = #{username}")
    Set<String> selectRolesByUsername(String username);


    @Insert("INSERT INTO user(`username`,`password`,`roles`)\n" +
            "values\n" +
            "(#{user.username},#{user.password},'${roles}');")
    int insertUser(User user,String roles);

3 Filter

登录拦截器

/**
 * 登录拦截器 - 请求登录接口/auth/login时会被拦截
 *  * 调用attemptAuthentication方法获取登录请求的(用户名,密码)等信息,
 *  * 调用authenticationManager.authenticate()让spring-security去进行验证
 *  * JWTAuthenticationFilter继承于UsernamePasswordAuthenticationFilter
 *  * 该拦截器用于获取用户登录的信息,只需创建一个token并调用authenticationManager.authenticate()让spring-security去进行验证就可以了,
 *  * 不用自己查数据库再对比密码了,这一步交给spring去操作。 这个操作有点像是shiro的subject.login(new UsernamePasswordToken()),
 *  * 验证的事情交给框架
 */
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    /**
     * 自定义登录URL(使用Security默认的登录地址为/login) - 根据项目需求配置
     * @param authenticationManager
     */
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

    /**
     * 第一步:获取用户参数,并进行校验(用户名密码)
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            /**从输入流中获取到登录的信息*/
            User loginUser = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 第二步:第一步方法成功验证后,则生成token并返回
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        /**获取上一步验证后的JwtUser对象*/
        JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
//        System.out.println(jwtUser);
        List<String> role = new ArrayList<>();
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        if(authorities != null && authorities.size() > 0){
            for (GrantedAuthority authority : authorities){
                role.add(authority.getAuthority());
            }
        }
        /**根据jwtUser信息该创建一个token*/
        String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role);
        /**
         * 1.返回创建成功的token
         * 2.按照jwt的规定,最后请求的时候应该是 "Bearer token"
         * 3.返回token加上"Bearer "前缀
         */
        response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
        PrintWriter writer = response.getWriter();
        //字符流输出
        writer.write(JwtTokenUtils.TOKEN_PREFIX + token);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("authentication failed, reason: " + failed.getMessage());
    }
}

Token解析及用户鉴权拦截器

/**
 * @Description:处理所有HTTP请求,并检查是否存在带有正确令牌的Authorization标头
 */
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 过滤器拦截方法
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        /**
         * 获取请求头中的token
         */
        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
        /**如果请求头中没有Authorization信息则直接执行放行(或者请求头中没有TOKEN_PREFIX前缀)*/
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            /**如果请求头中有token,则进行解析,并且设置认证信息*/
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        } catch (TokenIsExpiredException e) {
            /**返回json形式的错误信息*/
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            String reason = "统一处理,原因:" + e.getMessage();
            response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
            response.getWriter().flush();
            return;
        }
        super.doFilterInternal(request, response, chain);
    }

    /**
     * 将token进行解析,获取用户信息,并且设置认证信息(对Token进行校验)
     * @param tokenHeader
     * @return
     * @throws TokenIsExpiredException
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
        String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
        boolean expiration = JwtTokenUtils.isExpiration(token);
        if (expiration) {
            throw new TokenIsExpiredException("token超时了");
        } else {
            String username = JwtTokenUtils.getUserName(token);
            List<String> roles = JwtTokenUtils.getUserRole(token);
            if (username != null) {
                List<GrantedAuthority> list = new ArrayList<>();
                if(roles != null && roles.size() > 0){
                    for (String role : roles) {
                        list.add(new SimpleGrantedAuthority(role));
                    }
                }
                return new UsernamePasswordAuthenticationToken(username, null, list);
            }
        }
        return null;
    }
}

Exception

/**
 * @Description:没有访问权限
 */
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        String reason = "统一处理,原因(没有访问权限):" + e.getMessage();
        httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(reason));
    }
}
/**
 * @Description:没有携带token或者token无效
 */
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        String reason = "统一处理,原因(无token或token无效):" + authException.getMessage();
        response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
    }
}
/**
 * @Description:Token过期
 */
public class TokenIsExpiredException extends Exception {
    public TokenIsExpiredException() {}

    public TokenIsExpiredException(String message) {
        super(message);
    }

    public TokenIsExpiredException(String message, Throwable cause) {
        super(message, cause);
    }

    public TokenIsExpiredException(Throwable cause) {
        super(cause);
    }

    public TokenIsExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

SecurityConfig

/**
 * @Author: Tian Yang
 * @Date: 2022-08-22
 * @Description:
 * * 注释:
 *  * 注解@EnableWebSecurity开启Security功能
 *  * 注解@EnableGlobalMethodSecurity(prePostEnabled = true),Security默认是禁用注解的,要想开启注解需要此配置,其表达式时间方法级别的安全性4个注解:(
 *  * @PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问 例如:@PreAuthorize("hasAuthority('ADMIN')")表示用户具有admin角色,才能访问该方法
 *  * @PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
 *  * @PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
 *  * @PreFilter 允许方法调用,但必须在进入方法之前过滤输入值 )
 *
 **/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    /**
     * 对密码进行加密的类,配置在spring中,方便调用
     * 例如:bCryptPasswordEncoder.encode(registerUser.get("password"))
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * 重写configure方法,根据项目需求配置,并将将写好的相应配置类引入
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**cors()开启跨域*/
        http.cors()
                .and()
                /**禁用CSRF保护*/
                .csrf().disable()
                /**http.authorizeRequests()方法添加多个子项(.antMatchers(****))来指定URL的自定义要求*/
                .authorizeRequests()
                /**请求路径下的DELETE请求需要"ADMIN"权限才能操作,也可在接口上@PreAuthorize("hasAuthority('ADMIN')")*/
               .antMatchers(HttpMethod.DELETE, "/task/removeTaskList").hasAuthority("ADMIN")
                /**需要验证的资源*/
//            .antMatchers("/login/**").authenticated()
                /**匿名访问,无需鉴权*/
                .antMatchers("/auth/register").anonymous()
                /**静态资源放行*/
                .antMatchers("/**/*.png","/task/addTask","/step/addStep",
                        "/monitor/*").permitAll()
                /**对http所有的请求必须通过授权认证才可以访问*/
                .anyRequest().authenticated()
                /**放行所有请求*/
//                .anyRequest().permitAll()
                .and()
                /**添加自定义的登录拦截方式(JWTAuthenticationFilter配置类) 注意:顺序:JWTAuthenticationFilter必须在JWTAuthorizationFilter之前*/
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                /**添加自定义的请求拦截方式(JWTAuthorizationFilter配置类)*/
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                /**不需要session*/
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                /**自定义异常处理(没有携带token或者token无效,JWTAuthenticationEntryPoint类配置处理)*/
                .exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
                /**添加无权限时的处理(JWTAccessDeniedHandler类配置无访问权限异常处理)*/
                .accessDeniedHandler(new JWTAccessDeniedHandler());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

跨域问题

  1. 放在根目录
@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
        corsConfiguration.addAllowedHeader("*"); // 2允许任何头
        corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等)
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
}
  1. 放在config文件
@Configuration
public class CorsConfig implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest reqs = (HttpServletRequest) req;
        String curOrigin = reqs.getHeader("Origin");
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", curOrigin == null ? "true" : curOrigin);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
        response.setContentType("application/json;charset=UTF-8");
        chain.doFilter(req, res);
    }
    @Override
    public void init(FilterConfig filterConfig) {}
    @Override
    public void destroy() {}
}
~  ~  The   End  ~  ~


 赏 
感谢您的支持,我会继续努力哒!
支付宝收款码
tips
文章二维码 分类标签:技术springboot
文章标题:用户登录及鉴权
文章链接:http://120.46.217.131:82/archives/46/
最后编辑:2022 年 10 月 12 日 16:33 By Yang
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)

相关推荐

热门推荐

(*) 4 + 3 =
快来做第一个评论的人吧~