深入浅析springsecurity入门登录授权
①我们需要自定义登陆接口,也就是在controller目录新建LoginController类,在controller方法里面去调用service接口,在service接口实现AuthenticationManager去进行用户的认证,注意,我们定义的controller方法要让SpringSecurity对这个接口放行(如果不放行的话,会被SpringSecurity拦截),让用户访问这个接口的时候不用登录也能访问。
②在service接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器
③认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。
JwtAuthenticationTokenFilter过滤器类
package com.sangeng.filter; import com.sangeng.domain.LoginUser; import com.sangeng.utils.JwtUtil; import com.sangeng.utils.RedisCache; import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Objects; @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //获取token String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { //放行 filterChain.doFilter(request, response); return; } //解析token String userid; try { Claims claims = JwtUtil.parseJWT(token); userid = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("token非法"); } //从redis中获取用户信息 String redisKey = "login:" + userid; LoginUser loginUser = redisCache.getCacheObject(redisKey); if(Objects.isNull(loginUser)){ throw new RuntimeException("用户未登录"); } //存入SecurityContextHolder //TODO 获取权限信息封装到Authentication中 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); //放行 filterChain.doFilter(request, response); } }
在 LoginController类添加如下
package com.sangeng.controller; import com.sangeng.domain.ResponseResult; import com.sangeng.domain.User; import com.sangeng.service.LoginServcie; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class LoginController { @Autowired private LoginServcie loginServcie; @PostMapping("/user/login") public ResponseResult login(@RequestBody User user){ //登录 return loginServcie.login(user); } }
在service目录新建 LoginService 接口
package com.huanf.service; import com.huanf.domain.ResponseResult; import com.huanf.domain.User; import org.springframework.stereotype.Service; /** * @author 35238 * @date 2023/7/12 0012 11:45 */ @Service public interface LoginService { ResponseResult login(User user); }
LoginUser类
package com.sangeng.domain; import com.alibaba.fastjson.annotation.JSONField; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @Data @NoArgsConstructor public class LoginUser implements UserDetails { private User user; private List<String> permissions; public LoginUser(User user, List<String> permissions) { this.user = user; this.permissions = permissions; } @JSONField(serialize = false) private List<SimpleGrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { if(authorities!=null){ return authorities; } authorities = permissions.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return authorities; } @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; } }
在LoginServiceImpl类添加如下
package com.sangeng.service.impl; import com.sangeng.domain.LoginUser; import com.sangeng.domain.ResponseResult; import com.sangeng.domain.User; import com.sangeng.service.LoginServcie; import com.sangeng.utils.JwtUtil; import com.sangeng.utils.RedisCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.Objects; @Service public class LoginServiceImpl implements LoginServcie { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { //AuthenticationManager authenticate进行用户认证 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //如果认证没通过,给出对应的提示 if(Objects.isNull(authenticate)){ throw new RuntimeException("登录失败"); } //如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userid = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(userid); Map<String,String> map = new HashMap<>(); map.put("token",jwt); //把完整的用户信息存入redis userid作为key redisCache.setCacheObject("login:"+userid,loginUser); return new ResponseResult(200,"登录成功",map); } }
UserDetailsServiceImpl implements UserDetailsService
package com.sangeng.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.sangeng.domain.LoginUser; import com.sangeng.domain.User; import com.sangeng.mapper.MenuMapper; import com.sangeng.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private MenuMapper menuMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询用户信息 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUserName,username); User user = userMapper.selectOne(queryWrapper); //如果没有查询到用户就抛出异常 if(Objects.isNull(user)){ throw new RuntimeException("用户名或者密错误"); } // List<String> list = new ArrayList<>(Arrays.asList("system:dept:list","admin")); List<String> list = menuMapper.selectPermsByUserId(user.getId()); //把数据封装成UserDetails返回 return new LoginUser(user,list); } }
配置类SecurityConfig
package com.sangeng.config; import com.sangeng.filter.JwtAuthenticationTokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { //创建BCryptPasswordEncoder注入容器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录接口 允许匿名访问 .antMatchers("/user/login").anonymous() // .antMatchers("/testCors").hasAuthority("system:dept:list222") // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
MenuMapper接口
package com.sangeng.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.sangeng.domain.Menu; import java.util.List; public interface MenuMapper extends BaseMapper<Menu> { List<String> selectPermsByUserId(Long userid); }
MenuMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.sangeng.mapper.MenuMapper"> <select id="selectPermsByUserId" resultType="java.lang.String"> SELECT DISTINCT m.`perms` FROM sys_user_role ur LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id` LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id` LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id` WHERE user_id = #{userid} AND r.`status` = 0 AND m.`status` = 0 </select> </mapper>
第一步: 在你数据库的security 库,新建 sys_menu权限表、sys_role角色表、sys_role_menu中间表、sys_user_role中间表,并插入数据
create database if not exists security; use security; CREATE TABLE `sys_menu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名', `path` varchar(200) DEFAULT NULL COMMENT '路由地址', `component` varchar(255) DEFAULT NULL COMMENT '组件路径', `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', `perms` varchar(100) DEFAULT NULL COMMENT '权限标识', `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标', `create_by` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(20) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='权限表'; CREATE TABLE `sys_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL, `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串', `status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)', `del_flag` int(1) DEFAULT '0' COMMENT 'del_flag', `create_by` bigint(200) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(200) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; CREATE TABLE `sys_role_menu` ( `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID', `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜单id', PRIMARY KEY (`role_id`,`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; CREATE TABLE `sys_user_role` ( `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id', `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '角色id', PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; insert into sys_user_role values (2,1); insert into sys_role values (1,'经理','ceo',0,0,default,default,default,default,default), (2,'程序员','coder',0,0,default,default,default,default,default); insert into sys_role_menu values (1,1),(1,2); insert into sys_menu values (1,'部门管理','dept','system/dept/index',0,0,'system:dept:list','#',default,default,default,default,default,default), (2,'测试','test','system/test/index',0,0,'system:test:list','#',default,default,default,default,default,default) create database if not exists security; use security; CREATE TABLE `sys_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名', `nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称', `password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '码', `status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)', `email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱', `phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号', `sex` CHAR(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)', `avatar` VARCHAR(128) DEFAULT NULL COMMENT '头像', `user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)', `create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id', `create_time` DATETIME DEFAULT NULL COMMENT '创建时间', `update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人', `update_time` DATETIME DEFAULT NULL COMMENT '更新时间', `del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; insert into sys_user values (1,'admin','管理员','{noop}123456','0',DEFAULT,DEFAULT,DEFAULT,DEFAULT,'0',DEFAULT,DEFAULT,DEFAULT,DEFAULT,DEFAULT); insert into sys_user values (2,'huanf','涣沷a靑惷','$2a$10$YPnG.IYUk0mMechaxSibBuKmNeTzvuHdcxkqvoxizsll6WCQG9CHG','0',DEFAULT,DEFAULT,DEFAULT,DEFAULT,'1',DEFAULT,DEFAULT,DEFAULT,DEFAULT,DEFAULT);
授权的基本流程
在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限
所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication,然后设置我们的资源所需要的权限即可
自定义访问路径的权限
SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限
第一步: 在SecurityConfig配置类添加如下,作用是开启相关配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
HelloController类
package com.sangeng.controller; import com.sangeng.domain.ResponseResult; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") @PreAuthorize("@ex.hasAuthority('system:dept:list222')") // @PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')") // @PreAuthorize("hasRole('system:dept:list')") // @PreAuthorize("hasAnyRole('admin','sy stem:dept:list')") public ResponseResult hello(){ return new ResponseResult(200,"hello"); } }
SGExpressionRoot.class 自定义权限类
package com.sangeng.expression; import com.sangeng.domain.LoginUser; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import java.util.List; @Component("ex") public class SGExpressionRoot { public boolean hasAuthority(String authority){ //获取当前用户的权限 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); List<String> permissions = loginUser.getPermissions(); //判断用户权限集合中是否存在authority return permissions.contains(authority); } }
到此这篇关于springsecurity入门登录授权的文章就介绍到这了,更多相关springsecurity入门登录授权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot工程Docker多环境中使用同一个Jar包解决方案
在Docker多环境部署中,SpringBoot工程可以通过环境变量来动态改变配置,无需重新打包,利用volume挂载或docker cp命令,可以将配置文件直接传入容器,提高部署效率,并保证安全性2024-09-09java并发学习-CountDownLatch实现原理全面讲解
这篇文章主要介绍了java并发学习-CountDownLatch实现原理全面讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2021-02-02@PathVariable获取路径中带有 / 斜杠的解决方案
这篇文章主要介绍了@PathVariable获取路径中带有 / 斜杠的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-10-10
最新评论