博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springboot、springsecurity、jwt权限验证
阅读量:5099 次
发布时间:2019-06-13

本文共 12864 字,大约阅读时间需要 42 分钟。

1.背景

基于前后端分离项目的后端模块;

2.相关技术

  • springboot全家桶
    • web模块
    • security模块;用于权限的验证
    • mongodb 模块;集成mogodb模块
  • jwt 用于token的生成
  • mongodb
  • lomok
  • 后续会细分出更多的模块。用上springcloud全家桶

3.权限验证流程

3.1 构建User对象

实现security的UserDetail。之后所有权限获取都是从这个对象中返回

重写的默认属性必须返回true,不然在登录那块验证该属性是不是true。如果默认返回false,会报出各种用户相关的异常

@Data@JsonInclude(JsonInclude.Include.NON_NULL)public class JwtUser implements UserDetails {    private String username;    private String password;    private Collection
authorities; public JwtUser(String username, String password, Collection
authorities) { this.username = username; this.password = password; this.authorities = authorities; } @Override public Collection
getAuthorities() { return this.authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } /** * Indicates whether the user is locked or unlocked. A locked user cannot be * authenticated. * * @return true if the user is not locked, false otherwise */ @Override public boolean isAccountNonLocked() { return true; } /** * Indicates whether the user's credentials (password) has expired. Expired * credentials prevent authentication. * * @return true if the user's credentials are valid (ie non-expired), * false if no longer valid (ie expired) */ @Override public boolean isCredentialsNonExpired() { return true; } /** * Indicates whether the user is enabled or disabled. A disabled user cannot be * authenticated. * * @return true if the user is enabled, false otherwise */ @Override public boolean isEnabled() { return true; }

3.JwtUserDetailsServiceImpl

重写security的UserDaiService的loadByusername方法,实现自定义的权限验证

@Servicepublic class JwtUserDetailsServiceImpl implements UserDetailsService {    @Autowired    private UserService userService;    /**     * Locates the user based on the username. In the actual implementation, the search     * may possibly be case sensitive, or case insensitive depending on how the     * implementation instance is configured. In this case, the UserDetails     * object that comes back may have a username that is of a different case than what     * was actually requested..     *     * @param username the username identifying the user whose data is required.     * @return a fully populated user record (never null)     * @throws UsernameNotFoundException if the user could not be found or the user has no     *                                   GrantedAuthority     */    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{        //设置查询条件,邮箱是唯一的        User queryUser = new User();        queryUser.setEmail(username);        List
userList = null; try { userList = this.userService.getUser(queryUser); if (CollectionUtils.isEmpty(userList)) { //return new JwtUser(username, queryUser.getPwd(), authorities); throw new UsernameNotFoundException("用户账号:" + username + ",不存在"); } else { queryUser = userList.get(0); Set
authorities = new HashSet<>(); //获取该用户所有的权限信息 this.userService.getRoleByUserId(queryUser.getId()).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role.getRoleCode())); }); return new JwtUser(username, queryUser.getPwd(), authorities); } } catch (Exception e) { e.printStackTrace(); } return null; }}

3.3 token生成方法

@Componentpublic class JwtTokenUtil implements Serializable {    /**     * 密钥     */    private final String secret = "code4fun";    final static Long TIMESTAMP = 86400000L;    final static String TOKEN_PREFIX = "Bearer";    /**     * 从数据声明生成令牌     *     * @param claims 数据声明     * @return 令牌     */    private String generateToken(Map
claims) { Date expirationDate = new Date(System.currentTimeMillis() + TIMESTAMP); return TOKEN_PREFIX + " " +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; } /** * 生成令牌 * * @param userDetails 用户 * @return 令牌 */ public String generateToken(UserDetails userDetails) { Map
claims = new HashMap<>(2); claims.put("sub", userDetails.getUsername()); 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) { JwtUser user = (JwtUser) userDetails; String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token)); }}

3.4 token校验过滤器

每次请求的时候都会被该过滤器过滤拦截。主要是校验token的有效性

@Componentpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter {    @Autowired    private JwtUserDetailsServiceImpl userDetailsService;    private JwtTokenUtil jwtTokenUtil;    public JwtAuthenticationTokenFilter(JwtTokenUtil jwtTokenUtil) {        this.jwtTokenUtil = jwtTokenUtil;    }    /**     * 每个请求都被拦截     * Same contract as for {@code doFilter}, but guaranteed to be     * just invoked once per request within a single request thread.     * See {@link #shouldNotFilterAsyncDispatch()} for details.     * 

Provides HttpServletRequest and HttpServletResponse arguments instead of the * default ServletRequest and ServletResponse ones. * * @param request * @param response * @param filterChain */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); String tokenHead = "Bearer "; if (authHeader != null && authHeader.startsWith(tokenHead)) { String authToken = authHeader.substring(tokenHead.length()); String username = jwtTokenUtil.getUsernameFromToken(authToken); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { //返回jwtUser UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); //将该用户的权限信息存放到threadlocal中 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); }}

3.4 webSecurity配置

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private JwtUserDetailsServiceImpl userDetailsService;    @Autowired    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//    private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;//    private RestAccessDeniedHandler restAccessDeniedHandler;    @Autowired    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());    }    /**    * 注入密码BCryptPasswordEncoder    * 在添加用户的时候,要用 BCryptPasswordEncoder.encode()加密    * @return      */    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    @Bean    @Override    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }    @Override    protected void configure(HttpSecurity httpSecurity) throws Exception {        httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)                .and().authorizeRequests()                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()                .antMatchers("/user/**", "/login",                        "/js/**", "/bootstrap/**", "/css/**", "/images/**",  "/fonts/**").permitAll() //静态文件拦截                .anyRequest().authenticated()                .and().headers().cacheControl();        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);    }}

至此,相关的配置就配置完了。在登录操作的时候需要注意一下:

用户信息的验证全部交给spring security来操作,代码如下:

/**     * 登录操作,返回token     * @param userName     * @param password     * @return     * @throws Exception     */    @Override    public String login(String userName, String password) throws Exception {        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(userName, password);        Authentication authentication = authenticationManager.authenticate(upToken);        SecurityContextHolder.getContext().setAuthentication(authentication);        UserDetails userDetails = userDetailsService.loadUserByUsername(userName);        return jwtTokenUtil.generateToken(userDetails);    }

3.4 用户验证流程

UsernamePasswordAuthenticationTokenauthenticationManager.authenticate(upToken);//通过这个创建一个代理(ProviderManager)对象delegate = this.delegateBuilder.getObject();//调用代理对象的认证方法delegate.authenticate(authentication)    1.代理对象调用父类的 parent.authenticate(authentication);认证方法        1.进到parent.authenticate方法,去定ProvideManager的具体类型是DaoProviderManager    2.provider.authenticate(authentication);    //此时的provider是DaoProviderManager        1.判断参数authentication是不是UsernamePasswordAuthenticationToken类型;不是则跑出异常        2.取出唯一标识字段username            1.判断userCache是否包含user缓存                1.不在缓存中,创建user对象并存放到缓存中                    //调用这个方法转换成user对象                    1.user = retrieveUser(username,                        (UsernamePasswordAuthenticationToken) authentication);                        //调用用户自定义实现了UserDetailService的方法来获得user对象                        1.UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);                2.preAuthenticationChecks.check(user);                    1.preAuthenticationChecks.check校验上一部返回的user对象的属性,只要用户实现的userDetail的get,set方法赋上值就好了                  additionalAuthenticationChecks(user,                    (UsernamePasswordAuthenticationToken) authentication);                      1.uthentication.getCredentials() == null判断密码是不是为空                      2.presentedPassword = authentication.getCredentials().toString(); 获取页面传递过来的密码                      3.passwordEncoder.matches(presentedPassword, userDetails.getPassword())判断页面上传递过来的密码跟数据库中的密码是不是一致。                         1.调用BCrypt.checkpw(rawPassword.toString(), encodedPassword)比对                                1.调用 hashpw 来加密页面传递过来的密码信息。然后与数据库中的密码比对。如果相同则返回成功,不同则报错

3.5 开源地址

欢迎指导

后续将补上验证流程

转载于:https://www.cnblogs.com/KevinStark/p/10147823.html

你可能感兴趣的文章
android dialog使用自定义布局 设置窗体大小位置
查看>>
ionic2+ 基础
查看>>
查询消除重复行
查看>>
[leetcode]Minimum Path Sum
查看>>
内存管理 浅析 内存管理/内存优化技巧
查看>>
Aizu - 1378 Secret of Chocolate Poles (DP)
查看>>
csv HTTP简单表服务器
查看>>
IO流写出到本地 D盘demoIO.txt 文本中
查看>>
Screening technology proved cost effective deal
查看>>
mysql8.0.13下载与安装图文教程
查看>>
Thrift Expected protocol id ffffff82 but got 0
查看>>
【2.2】创建博客文章模型
查看>>
Kotlin动态图
查看>>
从零开始系列之vue全家桶(1)安装前期准备nodejs+cnpm+webpack+vue-cli+vue-router
查看>>
Jsp抓取页面内容
查看>>
大三上学期软件工程作业之点餐系统(网页版)的一些心得
查看>>
可选参数的函数还可以这样设计!
查看>>
[你必须知道的.NET]第二十一回:认识全面的null
查看>>
Java语言概述
查看>>
关于BOM知识的整理
查看>>