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&lt;? <span style="color: rgba(0, 0, 255, 1)">extends</span> GrantedAuthority&gt;<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&lt;? <span style="color: rgba(0, 0, 255, 1)">extends</span> GrantedAuthority&gt;<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&lt;String&gt; 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&lt;?&gt;<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&lt;?&gt;<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&lt;String, Object&gt;<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>&lt;String, Object&gt; claims = <span style="color: rgba(0, 0, 255, 1)">new</span> HashMap&lt;&gt;(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()) &amp;&amp; !<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> &amp;&amp;<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> &amp;&amp; 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 得设置等等的,这些都是可以任意更改的,无需纠结太多,根据根据个人习惯和当时的业务改就好了,至于到底怎样才是最好的,我也没太认真的去思考过,毕竟加班的时候只能先实现功能了,至于为什么在写这个博客的时候还不去思考的原因就是。。。因为这些并不是重点