SpringBoot整合SpringSecurity
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
tips:整合SpringSecurity时,使用到Redis和JWT.需要到的RedisUtil和JwtUitl自己封装好。
SpringSecurity自带用户认证
当SpringBoot只引入SpringSecurity,还没有配置任何配置和自定的时候,SpringSecurity会自动生成一个用户进行认证,用户名默认为user
,密码在控制台打印。
当访问接口的时候,会进行认证。SpringSecurity使用自动生成一个用户进行认证
一、让SpringSecurity使数据库中的用户名进行认证
需要继承的接口:
public interface UserDetailsService {
//通过重写这个方法,通过用户名到数据库查询出用户,将用户一些信息封装成UserDetails并返回
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
//将用户信息封装成UserDetails的实现类
public interface UserDetails extends Serializable {
//用户权限
Collection<? extends GrantedAuthority> getAuthorities();
//获取密码
String getPassword();
//用户名
String getUsername();
//根据用户情况设置用户状态
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
实现类
@Service
public class UserDetailsImpl implements UserDetailsService {
@Autowired
userMessageMapper userMessageMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
User user = (User) userMessageMapper.selectOne(new QueryWrapper<User>().eq("username",username));
//如果没有查询到用户,就抛出异常
if(Objects.isNull(user)){
//认证链会捕获
throw new RuntimeException("用户名错误");
}
//查询对应的权限信息(权限信息应从数据库中查询出来,这里为了方便,就先写死了)
ArrayList<String> permisson = new ArrayList<>(Arrays.asList("test","admin"));
//封装成UserDetails
LoginUser loginUser = new LoginUser(user,permisson);
return loginUser;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permission;
public LoginUser(User user,List<String> permission){
this.user=user;
this.permission=permission;
}
@JSONField(serialize = false)//SimpleGrantedAuthority无法序列化
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
//把permission中String类型的权限信息封装成SimplGrantedAuthority对象
authorities= new ArrayList<>();
permission.forEach((p)->{
authorities.add(new SimpleGrantedAuthority(p));
});
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
SpringSecurity配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//此时数据库中的密码不能为明文密码,应为BCryptPassword编码过的密码
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()
.authenticated()//所有请求都需要访问
;
//允许跨域
http.cors();
}
}
tips:到这里,前面SpringSecurity自动生成用户的部分,我们已经替换成自己的了,用户密码认证的时候SpringSecurity已经会去数据库查询并验证。验证通过则让继续访问接口,验证不通过,则不让访问接口。
进行登录认证和授权
@Service
public class LoginServiceImpl implements LoginService {
//AuthenticationManager anthenticate进行用户认证.需要在SpringSecurit容器ronrong
/**
* 在SpringSecurity中添加下面代码
* @Override
* @Bean
* public AuthenticationManager authenticationManagerBean() throws Exception {
* return super.authenticationManagerBean();
* }
*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisUtil redisUtil;
@Autowired
JwtUtil jwtUtil;
@Override
public Result login(User user) {
//如果认证没通过,给出对应的提示
//如果认证通过了,使用userid生成一个jwt存入ResponseResult返回
//把完整的用户信息存入redis userid作为key
UsernamePasswordAuthenticationToken usernamePassword= new UsernamePasswordAuthenticationToken(user.getName(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(usernamePassword);
if(Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
//认证通过,使用userId生成一个jwt,jwt存入Result返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = String.valueOf(loginUser.getUser().getId());
String name = loginUser.getUser().getName();
String jwt = jwtUtil.generateJWT(userId, name);
Map<String,String> map =new HashMap<>();
map.put("token",jwt);
//把完整的用户信息存入redis,userId作为key
redisUtil.stringSet("login:"+userId,loginUser);
return Result.success(200,"登录成功",map);
}
@Override
public Result logout() {
//获取SecurityContextHolder中的用户id
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
int id = loginUser.getUser().getId();
redisUtil.stringDelete("login:"+id);
//删除redis中的值
return Result.success("退出成功");
}
}
@RestController
public class LoginController {
@Autowired
LoginService loginService;
@PostMapping("/user/login")
public Result login(@RequestBody User user){
//登录
return loginService.login(user);
}
@PostMapping("/user/logout")
public Result logout(){
return loginService.logout();
}
}
整合JWT进行认证
编写JWT过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
JwtUtil jwtUtil;
@Autowired
RedisUtil redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
//没有token,放行,后面的过滤器会验证
filterChain.doFilter(request,response);
//不能往下继续执行
return;
}
String userId;
try{
Claims claims = jwtUtil.parseJWT(token);
userId = claims.getId();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("token非法");
}
//解析token
//从redis获取用户信息
//存入SecurityContextHolder
String redisKey="login:"+userId;
LoginUser loginUser = (LoginUser) redisUtil.stringGet(redisKey);
if(Objects.isNull(loginUser)){
throw new RuntimeException("token非法");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
自定义认证失败和授权失败处理器
//认证失败处理器
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Result fail = Result.fail(HttpStatus.UNAUTHORIZED.value(), "请求失败", "用户认证失败");
//处理异常
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
String message= JSON.toJSONString(fail);
response.getWriter().print(message);
}
}
//授权失败处理类
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
Result fail = Result.fail(HttpStatus.FORBIDDEN.value(), "请求失败", "权限不足");
String message = JSON.toJSONString(fail);
response.getWriter().print(message);
}
}
跨域配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")//跨域的路径
.allowedOriginPatterns("*")
.allowedHeaders("*")
.allowedMethods("GET","POST","DELETE","PUT")
.allowCredentials(true)
.maxAge(3600)
;
}
}
SpringSecurity完整配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/hello")
.permitAll()
//对于登录接口 允许匿名访问
.antMatchers("/user/login")
.anonymous()//登录之后不能再访问登录接口
.anyRequest()
.authenticated()
;
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
}
tips:SpringScurity整合完整。