基于先后端分离项目的后端模块;css
实现security的UserDetail。以后全部权限获取都是从这个对象中返回java
重写的默认属性必须返回true,否则在登陆那块验证该属性是否是true。若是默认返回false,会报出各类用户相关的异常git
@Data @JsonInclude(JsonInclude.Include.NON_NULL) public class JwtUser implements UserDetails { private String username; private String password; private Collection<? extends GrantedAuthority> authorities; public JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) { this.username = username; this.password = password; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> 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 <code>true</code> if the user is not locked, <code>false</code> otherwise */ @Override public boolean isAccountNonLocked() { return true; } /** * Indicates whether the user's credentials (password) has expired. Expired * credentials prevent authentication. * * @return <code>true</code> if the user's credentials are valid (ie non-expired), * <code>false</code> 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 <code>true</code> if the user is enabled, <code>false</code> otherwise */ @Override public boolean isEnabled() { return true; }
重写security的UserDaiService的loadByusername方法,实现自定义的权限验证github
@Service public 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 <code>UserDetails</code> * 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 <code>null</code>) * @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<User> 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<GrantedAuthority> 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; } }
@Component public 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<String, Object> 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<String, Object> 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)); } }
每次请求的时候都会被该过滤器过滤拦截。主要是校验token的有效性web
@Component public 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. * <p>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); } }
@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来操做,代码以下:spring
/** * 登陆操做,返回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); }
UsernamePasswordAuthenticationToken authenticationManager.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 来加密页面传递过来的密码信息。而后与数据库中的密码比对。若是相同则返回成功,不一样则报错
github地址 欢迎指导
后续将补上验证流程mongodb