SpringSecurity+jwt+Redis实现权限控制
认证流程![2022-05-01_20_32.png](https://img-blog.csdnimg.cn/img_convert/e26754ff622934f41b1c0388a8121997.png#clientId=u053830b8-c837-4&crop=0&crop=0&crop=1&crop=1&from=paste&height
·
认证流程
1.数据库表
user表:
CREATE TABLE `user_role` (
`id` char(20) COLLATE utf8_unicode_ci NOT NULL,
`user_id` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`role_id` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
roler表:
CREATE TABLE `role` (
`id` char(20) COLLATE utf8_unicode_ci NOT NULL,
`role_name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
** permission表:**
CREATE TABLE `permission` (
`id` char(20) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`path` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`component` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
user_role表:
CREATE TABLE `user_role` (
`id` char(20) COLLATE utf8_unicode_ci NOT NULL,
`user_id` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`role_id` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
role_permission表:
CREATE TABLE `role_permission` (
`id` char(20) COLLATE utf8_unicode_ci NOT NULL,
`role_id` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`permission_id` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
2.实现UserDetails接口
封装了用户信息以及相关的权限信息
public class MyUserDetails implements UserDetails {
private User user;
private List<String> permissionList;
public MyUserDetails() {
}
public MyUserDetails(User user, List<String> permissionList) {
this.user = user;
this.permissionList = permissionList;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<String> getPermissionList() {
return permissionList;
}
public void setPermissionList(List<String> permissionList) {
this.permissionList = permissionList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
for (String permission : permissionList) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
grantedAuthorityList.add(simpleGrantedAuthority);
}
return grantedAuthorityList;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
3.实现UserDetailsService接口
主要是通过loadUserByUsername()方法去数据库查询用户的信息和对应的权限
@Component
public class MyUserDetailsServiceImpl implements MyUserDetailsService {
@Autowired
IUserService userService;
@Autowired
IPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.getOne(new QueryWrapper<User>().eq("username", username));
if(user == null){
return null;
}
MyUserDetails userDetails = new MyUserDetails();
userDetails.setUser(user);
List<Permission> permissions = permissionService.getByUserId(user.getId());
List<String> list = new ArrayList<>();
for (Permission permission : permissions) {
String name = permission.getName();
list.add(name);
}
userDetails.setPermissionList(list);
return userDetails;
}
}
4.添加密码加密处理器类DefaultPasswordEncoder
/**
* 加密处理工具类
*/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder(){
this(-1);
}
public DefaultPasswordEncoder(int strength){
}
@Override
public String encode(CharSequence charSequence) {
return MD5.encrypt(charSequence.toString());
}
@Override
public boolean matches(CharSequence charSequence, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(charSequence.toString()));
}
public static void main(String[] args) {
DefaultPasswordEncoder defaultPasswordEncoder = new DefaultPasswordEncoder();
String encode = defaultPasswordEncoder.encode("456");
System.out.println(encode);
}
}
MD5工具类:
public final class MD5 {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}
}
5.添加过滤器和处理器
(1)登录认证过滤器UsernamePasswordAuthenticationFilter
- 在方法
attemptAuthentication()
里,获取前端传来的username和password,并将其封装成一个未认证的UsernamePasswordAuthenticationToken
,将这个类传给AuthenticationManager()
进行验证。 - 验证通过后调用方法
successfulAuthentication()
,根据用户信息生成对应的token,将用户对应的权限信息存储在redis服务器,并将这个token返回给前端 - 验证失败调用
unsuccessfulAuthentication()
方法,返回错误信息
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String pass_word = this.obtainPassword(request);
pass_word = pass_word != null ? pass_word : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, pass_word);
return this.getAuthenticationManager().authenticate(authRequest);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
MyUserDetails userDetails = (MyUserDetails) authResult.getPrincipal();
String token = tokenManager.createToken(userDetails.getUser().getUsername());
redisTemplate.opsForValue().set(userDetails.getUser().getUsername(), userDetails.getPermissionList());
ResponseUtil.out(response, ResponseVo.success(token));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, ResponseVo.fail(ReturnCode.INVALID_TOKEN.getCode(),"密码错误"));
}
}
jwt生成Token
@Component
public class TokenManager {
//token有效时长
private long tokenEcpiration = 24 * 60 * 60 * 1000;
//编码密钥
private String tokenSignKey = "123456";
//1.根据用户名生成token
public String createToken(String username){
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + tokenEcpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//2.根据token字符得到用户时间
public String getUserInfoFromToken(String token){
String usernfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return usernfo;
}
public void removeToken(String token){
}
public static void main(String[] args) {
TokenManager tokenManager = new TokenManager();
String token = tokenManager.createToken("wangwang");
System.out.println(token);
String userInfoFromToken = tokenManager.getUserInfoFromToken(token);
System.out.println(userInfoFromToken);
}
}
(2)登出处理器LogoutHandler
当用户发起\logout
请求时,触发TokenLoginoutHandler
,在redis服务器删除对应的用户权限信息
public class TokenLoginoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLoginoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String token = request.getHeader("token");
if(token != null){
String username = tokenManager.getUserInfoFromToken(token);
redisTemplate.delete(username);
ResponseUtil.out(response, ResponseVo.success("退出成功"));
}
}
}
(3)授权处理器BasicAuthenticationFilter
用户请求时,会携带token信息,经过该拦截器时会调用doFilterInternal()
这个方法,将从redis获取权限列表,并封装已认证的UsernamePasswordAuthenticationToken,并将这个类放到SecurityContextHolder.getContext()
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取当前认证成功的用户权限信息
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if(authentication != null){
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
String token = request.getHeader("token");
if(token != null){
String username = tokenManager.getUserInfoFromToken(token);
//从redis获取权限列表
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(username);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList){
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permissionValue);
authorities.add(simpleGrantedAuthority);
}
return new UsernamePasswordAuthenticationToken(username, token, authorities);
}
return null;
}
}
(4)授权失败处理AuthenticationEntryPoint
public class UnauthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, ResponseVo.fail(ReturnCode.RC403.getCode(),"无权限"));
}
}
6.添加配置器类
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启权限注解配置
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private MyUserDetailsService myUserDetailsService;
private DefaultPasswordEncoder passwordEncoder;
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
@Autowired
public SecurityConfig(MyUserDetailsService myUserDetailsService, DefaultPasswordEncoder passwordEncoder, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.myUserDetailsService = myUserDetailsService;
this.passwordEncoder = passwordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthEntryPoint())
.and().csrf().disable() //关闭csrf保护
.authorizeRequests()
.anyRequest().authenticated()//所有的请求都需要权限认证
.and().logout().logoutUrl("/logout")//配置登出请求
//配置登出对应的处理器
.addLogoutHandler(new TokenLoginoutHandler(tokenManager, redisTemplate))
.and()
//添加登录认证过滤器
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
//添加授权过滤器
.addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate));
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用自己的UserDetailsService和passwordEncoder
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder);
}
}
7.在方法上设置访问权限
@RestController
public class TestController {
@PreAuthorize("hasAuthority('admin.queryAll')")
@GetMapping("/hello")
String hello(){
return "hello,security!";
}
}
代码:
https://gitee.com/wangwang_520666/spring-security/tree/master/demo2
更多推荐
已为社区贡献1条内容
所有评论(0)