SpringSecurity实现登录和自定义权限认证
创始人
2024-03-25 00:17:37
0

介绍

        Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

        Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。根据自己的需要,可以使用适当的过滤器来保护自己的应用程序。

        本文权限认证不采用springsecurity的注解方式,使用自定义的权限认证,这样不需要在控制层的每个接口前面加上权限注解,具体请看下面的权限校验流程。

登录流程和权限校验流程

具体代码查看实现中的github地址

登录流程

        首先,系统会将输入的用户名和密码放入authentication中,之后进入UserDetailsService的实现类中,调用sql,通过用户名查询账号信息和角色信息,统一存储在UserDetails中,之后PasswordEncoder会将查询到的账号密码和authentication中密码进行比对,密码不一致,则抛出异常(认证失败),密码正确则开始执行登录的业务,使用jwt生成token,将UserDetails存储到redis中,然后将token和角色id集返回给客户端。

权限校验流程

        首先,进入自定义的过滤器中,获取客户端传的token,如果token为空,则进入下一层跳出该过滤器,进入配置中,查询是否为不需要权限认证的接口,不是则抛出异常(认证失败);如果token不为空,则使用jwt解析token,获取其中的userId,根据该userId查询redis中存储的用户信息,查询为空则抛出异常(账户过期,请重新登录),之后获取客户端传的role_id(角色id,因为一个用户可能有多个角色,因此需要传个角色id,告诉服务器当前角色),将客户端传的role_id和之前在redis中查询的用户信息比对,查看是否存在该角色,存在,则通过该role_id查询redis中该角色的权限,判断当前请求路径该角色是否拥有权限,有权限则将权限信息封装到authentication中,放行。

 

实现

具体代码请看github地址icon-default.png?t=M85Bhttps://github.com/cn-g/springsecurity

下面展示关键代码实现

springsecurity配置文件

因为新版本SpringSecurity弃用了WebSecurityConfigurerAdapter,所以新版的SpringSecurity需要更换SpringSecurity的配置文件,下面将新版和老版的springsecurity配置代码各自展示了一份

不继承WebSecurityConfigurerAdapter

import com.example.springsecurity.filter.JwtAuthenticationTokenFilter;
import com.example.springsecurity.filter.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;/*** @author Admin*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{@ResourceJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@ResourceAuthenticationEntryPointImpl authenticationEntryPoint;@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeHttpRequests()//登录接口,允许所有人访问.antMatchers("/user/login").permitAll()//除了上面的接口,其它接口都需要鉴权认证.anyRequest().authenticated();//配置登入认证失败、权限认证失败异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);//把token校验过滤器添加到过滤链中http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//允许跨域http.cors();return http.build();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {final List configurers = new ArrayList<>();configurers.add(new GlobalAuthenticationConfigurerAdapter() {@Overridepublic void configure(AuthenticationManagerBuilder auth){// auth.doSomething()}});return authConfig.getAuthenticationManager();}}

继承WebSecurityConfigurerAdapter

import com.example.springsecurity.filter.JwtAuthenticationTokenFilter;
import com.example.springsecurity.filter.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;/*** @author xu*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{@ResourceJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@ResourceAuthenticationEntryPointImpl authenticationEntryPoint;@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception{http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeHttpRequests()//登录接口,允许所有人访问.antMatchers("/user/login").permitAll()//除了上面的接口,其它接口都需要鉴权认证.anyRequest().authenticated();//配置登入认证失败、权限认证失败异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);//把token校验过滤器添加到过滤链中http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//允许跨域http.cors();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return supper.authenticationManagerBean();}}

过滤器实现

/*** token校验以及权限校验* * @author xu*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {// 放行filterChain.doFilter(request, response);return;}String userId;// 解析tokentry {Claims claims = JwtUtil.parseJWT(token);userId = claims.getSubject();} catch (Exception e) {logger.error("解析token失败");WebUtil.renderString(response, JSON.toJSONString(ResponseModels.loginException()));return;}// 从redis中获取用户信息String redisKey = "login:" + userId;LoginUser loginUser = redisCache.getCacheObject(redisKey);if (ObjectUtils.isEmpty(loginUser)) {logger.error("用户信息获取失败");WebUtil.renderString(response, JSON.toJSONString(ResponseModels.commonException("账户过期,请重新登录")));return;}String roleId = request.getHeader("role_id");if(ObjectUtils.isEmpty(roleId)){logger.error("无角色id");WebUtil.renderString(response, JSON.toJSONString(ResponseModels.loginException()));return;}// 校验是否有该角色if (!loginUser.getPermissions().contains(roleId)) {logger.error("角色不匹配");WebUtil.renderString(response, JSON.toJSONString(ResponseModels.noPowerException()));return;}String url = request.getRequestURI();String role = redisCache.getCacheObject("role:role_" + roleId);// 校验角色是否存在该路径if (!role.contains(url)) {logger.error("该接口路径无权限");WebUtil.renderString(response, JSON.toJSONString(ResponseModels.noPowerException()));return;}// 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());// 存入securityContextHolderSecurityContextHolder.getContext().setAuthentication(authenticationToken);// 放行filterChain.doFilter(request, response);}

UserDetailsServiceImpl实现:

/*** UserDetailsService实现类* * @author xu*/
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleMapper roleMapper;@Overridepublic UserDetails loadUserByUsername(String userName) {log.info("用户名称为:{}", userName);User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getUsername, userName).eq(User::getStatus, 0));if (ObjectUtils.isEmpty(user)) {throw new UsernameNotFoundException("用户名不存在");}// 获取当前用户角色信息List list = roleMapper.selectRoleByUserId(user.getId());log.info("用户的角色为:{}", list);//LoginUser为UserDetails的实现类return new LoginUser(user, list);}
}

登录、登出业务实现

    public ResponseModelDto login(User user) {UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(user.getUsername(),         user.getPassword());Authentication authentication =     authenticationManager.authenticate(authenticationToken);if (ObjectUtils.isEmpty(authentication)) {throw new CommonException("用户名或密码错误");}log.info("用户登录成功:{}", authentication);// 使用userId生成tokenLoginUser loginUser = (LoginUser)authentication.getPrincipal();String userId = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userId);redisCache.setCacheObject("login:" + userId, loginUser);// 获取当前用户角色信息List list = roleMapper.selectRoleByUserId(Long.valueOf(userId));HashMap map = new HashMap<>();map.put("token", jwt);map.put("role", String.join(",",list));//返回token和角色id集return ResponseModels.ok(map);}@Overridepublic ResponseModelDto logout() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser)authentication.getPrincipal();Long userId = loginUser.getUser().getId();redisCache.deleteObject("login:" + userId);return ResponseModels.ok("登出成功");}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...