Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统
首先呢就是需求:
1、账号、密码进行第一次登录,获得 token,之后的每次请求都在请求头里加上这个 token 就不用带账号、密码或是 session 了。
2、用户有两种类型,具体表现在数据库中存用户信息时是分开两张表进行存储的。
为什么会分开存两张表呢,这个设计的时候是先设计的表结构,有分开的必要所以就分开存了,也没有想过之后 Security 这块需要进行一些修改,但是分开存就分开存吧,Security 这块也不是很复杂。
maven 就是这两:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
然后直接说代码吧,首先呢是实现 dao 层,这一层就不贴代码了,反正就是能根据用户名能返回用户信息就好了。
所以其实第一步是实现自己的安全模型:
这一步是实现 UserDetails 这个接口,其中我额外添加了用户类型、用户 Id。其他的都是 UserDetails 接口必须实现的。
/** * 安全用户模型 * @author xuwang * Created on 2019/05/28 20:07 */ public class XWUserDetails implements UserDetails { //用户类型 code public final static String USER_TYPE_CODE = "1"; //管理员类型 code public final static String MANAGER_TYPE_CODE = "2"; //用户 id private Integer userId; //用户名 private String username; //密码 private String password; //用户类型 private String userType; //用户角色表 private Collection<? extends GrantedAuthority> authorities;</span><span style="color: rgba(0, 0, 255, 1)">public</span> XWUserDetails(Integer userId,String username, String password, String userType, Collection<? <span style="color: rgba(0, 0, 255, 1)">extends</span> GrantedAuthority><span style="color: rgba(0, 0, 0, 1)"> authorities){ </span><span style="color: rgba(0, 0, 255, 1)">this</span>.userId =<span style="color: rgba(0, 0, 0, 1)"> userId; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.username =<span style="color: rgba(0, 0, 0, 1)"> username; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.password =<span style="color: rgba(0, 0, 0, 1)"> password; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.userType =<span style="color: rgba(0, 0, 0, 1)"> userType; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.authorities =<span style="color: rgba(0, 0, 0, 1)"> authorities; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取权限列表 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> Collection </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> Collection<? <span style="color: rgba(0, 0, 255, 1)">extends</span> GrantedAuthority><span style="color: rgba(0, 0, 0, 1)"> getAuthorities() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> authorities; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取用户Id * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> String </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Integer getUserId() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> userId; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取用户类型 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> String </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getUserType() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> userType; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取密码 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> String </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getPassword() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> password; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取用户名 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> String </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getUsername() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> username; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 账号是否未过期 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> boolean </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isAccountNonExpired() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 账号是否未锁定 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> boolean </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isAccountNonLocked() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 凭证是否未过期 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> boolean </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isCredentialsNonExpired() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 账号是否已启用 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> boolean </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isEnabled() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; }</span></pre>
第二步是实现两个 UserDetailsService 因为要从两张表里进行查询,所以我就实现了两个 UserDetailsService
这一步呢,注入了 dao 层的东西,从数据库中查询出用户信息,构建 XWUserDetails 并返回。
/** * Manager 专用的 UserDetailsService * @author xuwang * Created on 2019/06/01 15:58 */ @Service("managerDetailsService") public class ManagerDetailsServiceImpl implements UserDetailsService { @Resource ScManagerMapper_Security scManagerMapper_security; @Resource ScRoleMapper_Security scRole_Mapper_security;</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 根据用户名从数据库中获取XWUserDetails * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> username * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> UserDetails * </span><span style="color: rgba(128, 128, 128, 1)">@throws</span><span style="color: rgba(0, 128, 0, 1)"> UsernameNotFoundException </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> UserDetails loadUserByUsername(String username) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> UsernameNotFoundException { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取用户信息</span> ScManager user =<span style="color: rgba(0, 0, 0, 1)"> scManagerMapper_security.findByUsername(username); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取角色列表</span> List<String> roles =<span style="color: rgba(0, 0, 0, 1)"> scRole_Mapper_security.findByUsername(username); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (user == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> UsernameNotFoundException(String.format("No user found with username '%s'."<span style="color: rgba(0, 0, 0, 1)">, username)); } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> XWUserDetails(user.getId(),user.getManagerName(), user.getLoginPass(),XWUserDetails.MANAGER_TYPE_CODE, roles.stream().map(SimpleGrantedAuthority::<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">).collect(Collectors.toList())); } }
}
/** * User 专用的 UserDetailsService * @author xuwang * Created on 2019/06/01 15:58 */ @Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { @Resource ScUserMapper_Security userMapper_security; @Resource ScRoleMapper_Security scRole_Mapper_security; /** * 根据用户名从数据库中获取 XWUserDetails * @param username * @return UserDetails * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //获取用户信息 ScUser user = userMapper_security.findByUsername(username); //获取角色列表 List<String> roles = scRole_Mapper_security.findByUsername(username); if (user == null) { throw new UsernameNotFoundException(String.format("No user found with username'%s'.", username));} else { return new XWUserDetails(user.getId(),user.getName(),user.getPassword(), XWUserDetails.MANAGER_TYPE_CODE, roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); } }}
第三步,实现两个 UsernamePasswordAuthenticationToken
这一步的话,其实单看不知道为什么实现两个类,但是注释里面我写了,然后真正的为什么,整体流程,到最后说吧。
/** * manager 专用的 UsernamePasswordAuthenticationToken * AuthenticationManager 会遍历使用 Provider 的 supports() 方法, 判断 AuthenticationToken 是不是自己想要的 * @author xuwang * Created on 2019/06/01 15:58 */ public class ManagerAuthenticationToken extends UsernamePasswordAuthenticationToken { public ManagerAuthenticationToken(Object principal, Object credentials) { super(principal, credentials); } }
/** * User 专用的 UsernamePasswordAuthenticationToken * AuthenticationManager 会遍历使用 Provider 的 supports() 方法, 判断 AuthenticationToken 是不是自己想要的 * @author xuwang * Created on 2019/06/01 15:58 */ public class UserAuthenticationToken extends UsernamePasswordAuthenticationToken { public UserAuthenticationToken(Object principal, Object credentials){ super(principal,credentials); } }
第四步,实现两个 AuthenticationProvider
这个地方用到了上面的两个类,重点是 supports() 方法,这个方法是用来校验传进来的 UsernamePasswordAuthenticationToken 的,反正就代表着这个 ManagerAuthenticationProvider 就只适用于 ManagerAuthenticationToken,另一个同理,具体也是最后说吧。
/** * Manager 专用的 AuthenticationProvider * 选择实现 DaoAuthenticationProvider 是因为比较方便且能用 * @author xuwang * Created on 2019/06/01 15:58 */ public class ManagerAuthenticationProvider extends DaoAuthenticationProvider { /** * 初始化 将使用 Manager 专用的 userDetailsService * @param encoder * @param userDetailsService */ public ManagerAuthenticationProvider(PasswordEncoder encoder, UserDetailsService userDetailsService){setPasswordEncoder(encoder); setUserDetailsService(userDetailsService); }@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setPasswordEncoder(PasswordEncoder passwordEncoder) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.setPasswordEncoder(passwordEncoder); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.setUserDetailsPasswordService(userDetailsPasswordService); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 判断只有传入ManagerAuthenticationToken的时候才使用这个Provider * supports会在AuthenticationManager层被调用 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> authentication * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> supports(Class<?><span style="color: rgba(0, 0, 0, 1)"> authentication) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> ManagerAuthenticationToken.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.isAssignableFrom(authentication); }
}
/** * 实现 User 专用的 AuthenticationProvider * 选择实现 DaoAuthenticationProvider 是因为比较方便且能用 * @author xuwang * Created on 2019/06/01 15:58 */ public class UserAuthenticationProvider extends DaoAuthenticationProvider {</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 初始化 将使用User专用的userDetailsService * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> encoder * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> userDetailsService </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> UserAuthenticationProvider(PasswordEncoder encoder, UserDetailsService userDetailsService){ setPasswordEncoder(encoder); setUserDetailsService(userDetailsService); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setPasswordEncoder(PasswordEncoder passwordEncoder) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.setPasswordEncoder(passwordEncoder); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.setUserDetailsPasswordService(userDetailsPasswordService); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 判断只有传入UserAuthenticationToken的时候才使用这个Provider * supports会在AuthenticationManager层被调用 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> authentication * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> supports(Class<?><span style="color: rgba(0, 0, 0, 1)"> authentication) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> UserAuthenticationToken.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.isAssignableFrom(authentication); }
}
第五步就是继承实现这个 WebSecurityConfigurerAdapter
这一步呢,主要是将上面两个 AuthenticationProvider 加入到 AuthenticationManager 中,并向 Spring 中注入这个 AuthenticationManager 供 Service 在校验账号密码时使用。
同时还注入了一个 PasswordEncoder,也是同样供 Service 层使用,反正就是其他地方能用就是了,就不用 new 了。
然后是 configure 方法,这个里面,具体就是 Security 的配置了,为什么怎么写我就不说了,反正我这里实现了 url 的配置、Session 的关闭、Filter 的设置、设置验证失败权限不足自定义返回值。
其中 Filter、和验证失败权限不足再看后面的代码吧,我也会贴上的。
关于 AuthenticationManager,就是先用加密工具、和之前实现的 UserDetailsService 构造两个 DaoAuthenticationProvider,然后在 configureGlobal()方法中添加这两个 DaoAuthenticationProvider,最后 authenticationManagerBean() 方法进行注入。
/** * Security 配置 * @author xuwang * Created on 2019/06/01 15:58 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class XWSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("userDetailsService") private UserDetailsService userDetailsService; @Autowired @Qualifier("managerDetailsService") private UserDetailsService managerDetailsService; @Resource private XWAuthenticationTokenFilter xwAuthenticationTokenFilter; @Resource private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler; @Resource private RestAccessDeniedHandler restAccessDeniedHandler;</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 注入UserAuthenticationProvider * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Bean(</span>"UserAuthenticationProvider"<span style="color: rgba(0, 0, 0, 1)">) DaoAuthenticationProvider daoUserAuthenticationProvider(){ </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UserAuthenticationProvider(encoder(), userDetailsService); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 注入ManagerAuthenticationProvider * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Bean(</span>"ManagerAuthenticationProvider"<span style="color: rgba(0, 0, 0, 1)">) DaoAuthenticationProvider daoMangerAuthenticationProvider(){ </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ManagerAuthenticationProvider(encoder(), managerDetailsService); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 向AuthenticationManager添加Provider * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Autowired </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> configureGlobal(AuthenticationManagerBuilder auth){ auth.authenticationProvider(daoUserAuthenticationProvider()); auth.authenticationProvider(daoMangerAuthenticationProvider()); } @Autowired </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { authenticationManagerBuilder.userDetailsService(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.userDetailsService).passwordEncoder(passwordEncoder); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 注入AuthenticationManager * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span> AuthenticationManager authenticationManagerBean() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.authenticationManagerBean(); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 注入PasswordEncoder * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> PasswordEncoder encoder() { PasswordEncoder encoder </span>=<span style="color: rgba(0, 0, 0, 1)"> PasswordEncoderFactories.createDelegatingPasswordEncoder(); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> encoder; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 具体Security 配置 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> configure(HttpSecurity http) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { http. csrf().disable().</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">默认开启,这里先显式关闭csrf</span> sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Spring Security永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext</span>
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/").permitAll() //任何用户任意方法可以访问 /
.antMatchers("/base/login").permitAll() //任何用户可以访问 /user/**
.anyRequest().authenticated() //任何没有匹配上的其他的 url 请求,只需要用户被验证
.and()
.headers().cacheControl();
http.addFilterBefore(xwAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler);
}}
然后是上面的两个 Handler
这个很简单,就是实现 AccessDeniedHandler 和 AuthenticationEntryPoint 就是了。
/** * 身份验证失败自定 401 返回值 * * @author xuwang * Created on 2019/05/29 16:10. */ @Component public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); httpServletResponse.setStatus(401);} }
/** * 权限不足自定 403 返回值 * * @author xuwang * Created on 2019/05/29 16:10. */ @Component public class RestAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); httpServletResponse.setStatus(403);} }
再然后就是 JWT 这一块了。
首先是一个 Token 的工具类,里面有些什么东西直接看注释就好了。
/** * JWT 工具类 * * @author xuwang * Created on 2019/05/28 20:16. */ @Component public class XWTokenUtil implements Serializable {</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 密钥 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String secret = "11111111"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 从数据声明生成令牌 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> claims 数据声明 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 令牌 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">private</span> String generateToken(Map<String, Object><span style="color: rgba(0, 0, 0, 1)"> claims) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">有效时间</span> Date expirationDate = <span style="color: rgba(0, 0, 255, 1)">new</span> Date(System.currentTimeMillis() + 2592000L * 1000<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact(); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 从令牌中获取数据声明 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 数据声明 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Claims getClaimsFromToken(String token) { Claims claims; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { claims </span>=<span style="color: rgba(0, 0, 0, 1)"> Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { claims </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> claims; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 生成令牌 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> userDetails 用户 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 令牌 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String generateToken(UserDetails userDetails) { Map</span><String, Object> claims = <span style="color: rgba(0, 0, 255, 1)">new</span> HashMap<>(2<span style="color: rgba(0, 0, 0, 1)">); claims.put(</span>"sub"<span style="color: rgba(0, 0, 0, 1)">, userDetails.getUsername()); claims.put(</span>"userId"<span style="color: rgba(0, 0, 0, 1)">, ((XWUserDetails)userDetails).getUserId()); claims.put(</span>"userType"<span style="color: rgba(0, 0, 0, 1)">, ((XWUserDetails)userDetails).getUserType()); claims.put(</span>"created", <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date()); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> generateToken(claims); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 从令牌中获取用户名 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 用户名 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getUsernameFromToken(String token) { String username; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { Claims claims </span>=<span style="color: rgba(0, 0, 0, 1)"> getClaimsFromToken(token); username </span>=<span style="color: rgba(0, 0, 0, 1)"> claims.getSubject(); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { username </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> username; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 从令牌中获取用户类型 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 用户类型 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getUserTypeFromToken(String token) { String userType; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { Claims claims </span>=<span style="color: rgba(0, 0, 0, 1)"> getClaimsFromToken(token); userType </span>= (String) claims.get("userType"<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { userType </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> userType; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 从令牌中获取用户Id * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 用户Id </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Integer getUserIdFromToken(String token) { Integer userId; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { Claims claims </span>=<span style="color: rgba(0, 0, 0, 1)"> getClaimsFromToken(token); userId </span>= (Integer) claims.get("userId"<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { userId </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> userId; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 判断令牌是否过期 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 是否过期 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Boolean isTokenExpired(String token) { </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { Claims claims </span>=<span style="color: rgba(0, 0, 0, 1)"> getClaimsFromToken(token); Date expiration </span>=<span style="color: rgba(0, 0, 0, 1)"> claims.getExpiration(); </span><span style="color: rgba(0, 0, 255, 1)">return</span> expiration.before(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date()); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">; } } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 刷新令牌 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 原令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 新令牌 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String refreshToken(String token) { String refreshedToken; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { Claims claims </span>=<span style="color: rgba(0, 0, 0, 1)"> getClaimsFromToken(token); claims.put(</span>"created", <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date()); refreshedToken </span>=<span style="color: rgba(0, 0, 0, 1)"> generateToken(claims); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { refreshedToken </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> refreshedToken; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 验证令牌 * * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> token 令牌 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> userDetails 用户 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 是否有效 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Boolean validateToken(String token, UserDetails userDetails) { XWUserDetails user </span>=<span style="color: rgba(0, 0, 0, 1)"> (xwUserDetails) userDetails; String username </span>=<span style="color: rgba(0, 0, 0, 1)"> getUsernameFromToken(token); </span><span style="color: rgba(0, 0, 255, 1)">return</span> (username.equals(user.getUsername()) && !<span style="color: rgba(0, 0, 0, 1)">isTokenExpired(token)); }
}
然后是 Filter
这个 Filter 大家都知道请求发过来,会先进行这个 Filter 里面的方法,这里的逻辑也很简单,从 Token 中拿到身份信息,并进行验证,验证这里我写得比简单,可以再加逻辑。
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
重点是这三行,这三行是什么意思呢,前面说了需求是第一次登录验证成功,以后发请求使用 Token 就好了,这三行之前的逻辑是在校验 Token,从 Token 中获取用户信息,但系统中进行权限管理的是 Spring Security,并没有使用 Spring Security 进行验证啊,
所以需要做的就是这三行,这三行中的 SecurityContextHolder 就是:SecurityContextHolder 是用来保存 SecurityContext 的。SecurityContext 中含有当前正在访问系统的用户的详细信息,
实际就是使用用户信息构建 authentication 放到 SecurityContextHolder 就等于用户已经登录了,就不用再校验密码什么的了。
/** * JWT Filter * * @author xuwang * Created on 2019/05/29 16:10. */ @Component public class XWAuthenticationTokenFilter extends OncePerRequestFilter {@Resource ManagerDetailsServiceImpl managerDetailsService; @Resource UserDetailsServiceImpl userDetailsService; @Resource </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> XWTokenUtil xwTokenUtil; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取验证token中的身份信息 * </span><span style="color: rgba(128, 128, 128, 1)">@author</span><span style="color: rgba(0, 128, 0, 1)"> xuwang </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ServletException, IOException { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">从请求头中获取token</span> String authHeader = request.getHeader("Authorization"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">token前缀</span> String tokenHead = "Bearer "<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (authHeader != <span style="color: rgba(0, 0, 255, 1)">null</span> &&<span style="color: rgba(0, 0, 0, 1)"> authHeader.startsWith(tokenHead)) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">去掉token前缀</span> String authToken =<span style="color: rgba(0, 0, 0, 1)"> authHeader.substring(tokenHead.length()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">从token中获取用户名</span> String username =<span style="color: rgba(0, 0, 0, 1)"> XWTokenUtil.getUsernameFromToken(authToken); String userType </span>=<span style="color: rgba(0, 0, 0, 1)"> XWTokenUtil.getUserTypeFromToken(authToken); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (username != <span style="color: rgba(0, 0, 255, 1)">null</span> && SecurityContextHolder.getContext().getAuthentication() == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { UserDetails userDetails </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">根据从token中获取用户名从数据库中获取一个userDetails</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(userType.equals(XWUserDetails.USER_TYPE_CODE)){ </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">普通用户</span> userDetails =<span style="color: rgba(0, 0, 0, 1)"> userDetailsService.loadUserByUsername(username); }</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(userType.equals(XWUserDetails.MANAGER_TYPE_CODE)){ </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">管理员</span> userDetails =<span style="color: rgba(0, 0, 0, 1)"> managerDetailsService.loadUserByUsername(username); } </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (xwTokenUtil.validateToken(authToken, userDetails)) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">token中的用户信息和数据库中的用户信息对比成功后将用户信息加入SecurityContextHolder相当于登陆</span> UsernamePasswordAuthenticationToken authentication = <span style="color: rgba(0, 0, 255, 1)">new</span> UsernamePasswordAuthenticationToken(userDetails, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, userDetails.getAuthorities()); authentication.setDetails(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); }
}
然后是用户登录的接口了,我直接贴 Service 层的代码吧
/** * @ClassName: loginServiceImpl * @ClassNameExplain: * @Description: 业务层实现类 * @author xuwang * @date 2019-05-31 16:15:46 */ @Service public class LoginServiceImpl implements ILoginService {</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Logger logger = LoggerFactory.getLogger(LoginServiceImpl.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); @Resource </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> AuthenticationManager authenticationManager; @Autowired @Qualifier(</span>"userDetailsService"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> UserDetailsService userDetailsService; @Autowired @Qualifier(</span>"managerDetailsService"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> UserDetailsService managerDetailsService; @Resource </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> XWTokenUtil xwTokenUtil; @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> LoginVO login(LoginIO loginIO) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">不同的用户类型使用不同的登陆方式</span> String token = ""<span style="color: rgba(0, 0, 0, 1)">; UserDetails userDetails </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(loginIO.getType().equals(XWUserDetails.USER_TYPE_CODE)){ </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">登录</span> login(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UserAuthenticationToken(loginIO.getUserName(), loginIO.getPassword())); userDetails </span>=<span style="color: rgba(0, 0, 0, 1)"> userDetailsService.loadUserByUsername(loginIO.getUserName()); token </span>=<span style="color: rgba(0, 0, 0, 1)"> xwTokenUtil.generateToken(userDetails); logger.info(</span>"user[{}]登陆成功"<span style="color: rgba(0, 0, 0, 1)">,loginIO.getUserName()); }</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(loginIO.getType().equals(XWUserDetails.MANAGER_TYPE_CODE)){ login(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ManagerAuthenticationToken(loginIO.getUserName(), loginIO.getPassword())); userDetails </span>=<span style="color: rgba(0, 0, 0, 1)"> managerDetailsService.loadUserByUsername(loginIO.getUserName()); token </span>=<span style="color: rgba(0, 0, 0, 1)"> xwUtil.generateToken(userDetails); logger.info(</span>"manager[{}]登陆成功"<span style="color: rgba(0, 0, 0, 1)">,loginIO.getUserName()); }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { logger.error(</span>"type[{}]参数错误"<span style="color: rgba(0, 0, 0, 1)">,loginIO.getType()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">type参数错误</span> <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BusinessException(ExceptionConstants.PARAM_INVALID_CODE, ExceptionConstants.PARAM_INVALID_MSG); } LoginVO loginVO </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> LoginVO(); loginVO.setToken(token); loginVO.setUserId(((XWUserDetails)userDetails).getUserId()); </span><span style="color: rgba(0, 0, 255, 1)">return</span> loginVO == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> LoginVO() : loginVO; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 校验账号密码并进行登陆 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> upToken </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> login(UsernamePasswordAuthenticationToken upToken){ </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">验证</span> Authentication authentication =<span style="color: rgba(0, 0, 0, 1)"> authenticationManager.authenticate(upToken); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将用户信息保存到SecurityContextHolder=登陆</span>
SecurityContextHolder.getContext().setAuthentication(authentication);
}}
这个 Service 解释一下就是:
loginIO 能接收到用户信息: 账号 UserName、密码 Password、类型 Type 之类的,然后使用 AuthenticationManager 进行校验,再使用 SecurityContextHolder 进行登录操作 (上面解释过了),最后返回 Token(xwTokenUtil 工具类生成的) 和用户 Id。
最后解释一下其中的流程,已经我为什么这么去实现吧。
从 Service 中我们可以看到,登录时使用的是先使用账号密码构建了一个 UsernamePasswordAuthenticationToken,我这里构建的是 UserAuthenticationToken、ManagerAuthenticationToken,不过影响不大,都是它的子类,AuthenticationManager 的 authenticate 将接受一个 UsernamePasswordAuthenticationToken 来进行验证,最后才登录。
上面的相当于 Security 的登录使用流程。
然后解释一下前面的那些所有的疑惑,在 Service 中使用 AuthenticationManager 的 authenticate()方法进行校验的时候,实际上是会把 UsernamePasswordAuthenticationToken 传递给 Provider 进行校验的,Provider 里呢又让 Service 去校验的。这是类和类之间的关系,然后还有实际代码关系是,AuthenticationManager 中会有一个 Provider 列表,进行校验的时候会遍历使用每一个 Provider 的 supports() 方法,这个 supports()方法将校验传进来的 UsernamePasswordAuthenticationToken 是自己想要的 UsernamePasswordAuthenticationToken 吗,如果是的话就使用这个 Provider 进行校验。所以我实现了 UserAuthenticationToken、ManagerAuthenticationToken,还实现了 ManagerAuthenticationProvider、UserAuthenticationProvider 以及其中的 supports() 方法,这样 authenticationManager.authenticate(new UserAuthenticationToken) 就会使用 UserAuthenticationProvider 中的 UserDetailsServiceImpl 去校验了。ManagerAuthenticationToken 同理。
上面的我自己是觉得写得是比较清晰了。如果实在是看不明白,或者其实是我还是写得太烂了,可以自己跟一下代码,就从 AuthenticationManager.authenticate() 方法跟进去就好了,
具体跟代码的时候要注意,AuthenticationManager 的默认实现是 ProviderManager,所以其实看到的是 ProviderManager 的 authenticate() 方法
图中:
1. 获取到 Authentication 的类信息
2. 得到 Provider 列表的迭代器
3. 进行遍历
4. 调用 Provider 的 supports() 方法
所以我重写了两个 provider 和其中 supports() 方法,和两个 AuthenticationToken。
然后其实这个东西并不算是太复杂,自己去用和学习的时候,最好还是先实现,然后在慢慢跟代码,去猜去思考其中的流程,就好了。
最后是为什么要这样去写代码、去注入、用这个方式进行加密、以及 token 中存放的信息、loginIo 得设置等等的,这些都是可以任意更改的,无需纠结太多,根据根据个人习惯和当时的业务改就好了,至于到底怎样才是最好的,我也没太认真的去思考过,毕竟加班的时候只能先实现功能了,至于为什么在写这个博客的时候还不去思考的原因就是。。。因为这些并不是重点