SpringSecurity实战(二)-基于数据库认证
pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.32</version> </dependency> <!-- 引入Druid依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!--security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--security单元测试--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!-- redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- json--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <!-- jwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.13</version> </dependency>
2.数据库以及实体类
3.准备登录接口
import com.example.securitydemo.api.entity.SysUser; import com.example.securitydemo.api.utils.Result; import com.example.securitydemo.security.login.service.LoginService; 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; /** * @author 80396132 * @date 2023/5/10 9:51 */ @RestController public class LoginController { @Autowired private LoginService loginService; @PostMapping("/user/login") public Result login(@RequestBody SysUser user) { return loginService.login(user); } @PostMapping("/user/logout") public Result logout() { return loginService.logout(); } }
4.Security配置
import com.example.securitydemo.security.login.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; /** * @author 80396132 * @date 2023/5/10 9:49 */ @EnableGlobalMethodSecurity(prePostEnabled = true) //开启授权注解功能 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录接口 允许匿名访问 .antMatchers("/user/login").anonymous() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //把token校验过滤器添加到过滤器链中 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
5.登录接口和实现类
import com.example.securitydemo.api.entity.SysUser; import com.example.securitydemo.api.utils.Result; /** * @author 80396132 */ public interface LoginService { Result login(SysUser user); Result logout(); }
import com.example.securitydemo.api.entity.SysUser; import com.example.securitydemo.api.utils.JwtUtil; import com.example.securitydemo.api.utils.RedisCache; import com.example.securitydemo.api.utils.Result; import com.example.securitydemo.security.login.entity.LoginUser; 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.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @author 80396132 * @date 2023/5/10 9:58 */ @Service public class LoginServiceImpl implements LoginService { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public Result login(SysUser user) { //通过UsernamePasswordAuthenticationToken获取用户名和密码 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()); //AuthenticationManager委托机制对authenticationToken 进行用户认证 Authentication authenticate = authenticationManager.authenticate(authenticationToken); //如果认证没有通过,给出对应的提示 if (Objects.isNull(authenticate)) { throw new RuntimeException("登录失败"); } //如果认证通过,使用user生成jwt jwt存入Result 返回 //如果认证通过,拿到这个当前登录用户信息 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); //获取当前用户的userid String userid = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(userid); Map<String, String> map = new HashMap<>(); map.put("token", jwt); //把完整的用户信息存入redis userid为key 用户信息为value redisCache.setCacheObject("login:" + userid, loginUser); return new Result(200, "登录成功", map); } @Override public Result logout() { //从SecurityContextHolder中的userid UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); Long userid = loginUser.getUser().getId(); //根据userid找到redis对应值进行删除 redisCache.deleteObject("login:" + userid); return new Result(200, "注销成功"); } }
5.实现UserDetails 的存储用户信息类
import com.example.securitydemo.api.entity.SysUser; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.List; /** * @author 80396132 * @date 2023/5/10 9:47 */ @Data @NoArgsConstructor @AllArgsConstructor public class LoginUser implements UserDetails { private SysUser user; @Override public List<GrantedAuthority> getAuthorities() { return null; } @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; } }
6.实现UserDetailsService 的自定义认证
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.securitydemo.api.entity.SysUser; import com.example.securitydemo.api.mapper.SysUserMapper; import com.example.securitydemo.security.login.entity.LoginUser; 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 org.springframework.util.ObjectUtils; import javax.annotation.Resource; /** * @author 80396132 * @date 2023/5/10 9:42 */ @Service public class UserDetailServiceImpl implements UserDetailsService { @Resource private SysUserMapper userMapper; //实现UserDetailsService接口,重写UserDetails方法,自定义用户的信息从数据中查询 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //(认证,即校验该用户是否存在)查询用户信息 LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getUserName, username); SysUser user = userMapper.selectOne(queryWrapper); //如果没有查询到用户 if (ObjectUtils.isEmpty(user)) { throw new RuntimeException("用户名或者密码错误"); } //TODO (授权,即查询用户具有哪些权限)查询对应的用户信息 //把数据封装成UserDetails返回 return new LoginUser(user); } }
7.token拦截器
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.example.securitydemo.api.utils.JwtUtil; import com.example.securitydemo.api.utils.RedisCache; import com.example.securitydemo.security.login.entity.LoginUser; 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; /** * @author 80396132 * @date 2023/5/10 10:01 */ @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; JSONObject cacheObject = redisCache.getCacheObject(redisKey); LoginUser loginUser = JSON.toJavaObject(cacheObject, LoginUser.class); if (Objects.isNull(loginUser)) { throw new RuntimeException("用户未登录"); } //封装Authentication对象存入SecurityContextHolder //TODO 获取权限信息封装到Authentication中 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); //放行 filterChain.doFilter(request, response); } }