提前打开Redis
1)通过内置的用户名和密码登录
spring-boot-starter-security.jar
2)使用自定义用户名和密码登录
UserDetailService
自定义类实现UserDetailService接口,重写loadUserByUsername方法
class UserDetailServiceImpl implements UserDetailService{
public UserDetails loadUserByUsername(String username){
//查询数据库表
//获取用户信息
SysUser user = mapper.方法();
//封装到UserDetails对象中
LoginUser loginUser = new LoginUser(user);
}
}
class LoginUser implements UserDetails{
private SysUser sysUser;
public LoginUser(SysUser user){
this.sysUser = user;
}
getUsername(){
return "用户名"
}
getPassword(){}
get....
}
3)加密功能 bcryptPasswordEncoder
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/*
创建加密对象(密码匹配器对象)
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
4)自定义登录接口
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@RequestMapping("/login")
public R login(String username, String password) throws AuthenticationException {
//调用service
return loginService.login(username, password);
}
}
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public R login(String username, String password) throws AuthenticationException {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password);
//调用认证提供器的认证方法,进行用户名,密码认证
Authentication authentication = authenticationManager.authenticate(token);
//根据返回值判断是否认证成功
if(authentication.isAuthenticated()){//认证成功
//获取用户身份 LoginUser
LoginUser user = (LoginUser) authentication.getPrincipal();
//获取用户id
Long id = user.getSysUser().getId();
//根据用户id,生成token
String token2 = JwtUtil.createJWT(id+"");
//返回 code ,msg,token
return R.ok(token2,"认证成功");
}
return null;
}
}
5)登录成功后缓存用户信息到redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
//将用户信息存储到redis中
redisTemplate.opsForValue().set(id,user,30, TimeUnit.MINUTES);
//将用户信息存储到SecurityContext上下文环境中,供其他过滤器使用
SecurityContextHolder.getContext().setAuthentication(authentication);
完整代码如下:
package com.hl.springsecurity01.service.impl;
import com.hl.springsecurity01.domain.R;
import com.hl.springsecurity01.security.LoginUser;
import com.hl.springsecurity01.service.LoginService;
import com.hl.springsecurity01.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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 javax.security.sasl.AuthenticationException;
import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate redisTemplate;
@Override
public R login(String username, String password) throws AuthenticationException {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password);
//调用认证提供器的认证方法,进行用户名,密码认证
Authentication authentication = authenticationManager.authenticate(token);
//根据返回值判断是否认证成功
if(authentication == null){
//认证失败
throw new AuthenticationException("用户名或者密码错误");
}
if(authentication.isAuthenticated()){//认证成功
//获取用户身份 LoginUser
LoginUser user = (LoginUser) authentication.getPrincipal();
//获取用户id
Long id = user.getSysUser().getId();
//将用户信息存储到redis中
redisTemplate.opsForValue().set(id,user,30, TimeUnit.MINUTES);
//将用户信息存储到SecurityContext上下文环境中,供其他过滤器使用
SecurityContextHolder.getContext().setAuthentication(authentication);
//根据用户id,生成token
String token2 = JwtUtil.createJWT(id+"");
//返回 code ,msg,token
return R.ok(token2,"认证成功");
}
return null;
}
}
6)携带token,访问目标方法
创建过滤器并配置过滤器
/*
创建token过滤器
*/
@Component
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("到达jwt过滤器.....");
//放行,到达目标方法
filterChain.doFilter(request,response);
}
}
package com.hl.springsecurity01.security;
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.web.WebSecurityConfigurer;
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;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JWTAuthenticationTokenFilter authenticationTokenFilter;
/*
创建加密对象(密码匹配器对象)
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//配置自定义过滤器
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
token过滤器完整代码
package com.hl.springsecurity01.security;
import com.hl.springsecurity01.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
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;
/*
创建token过滤器
*/
@Component
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("到达jwt过滤器.....");
//获取请求头中的token
String token = request.getHeader("token");
if(token == null){
// throw new RuntimeException("token不能为空!");
System.out.println("token为空!");
//放行,到usernamePasswordtoken
filterChain.doFilter(request,response);
return;
}
//校验token是否合法
Long userId = null;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = Long.parseLong(claims.getSubject());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token 不合法");
}
//判断用户是否登录成功,服务端是否存在该用户信息
Object obj = redisTemplate.opsForValue().get(userId);
if(obj == null){
System.out.println("用户未登录");
throw new RuntimeException("用户未登录!");
}
//将登录成功的用户信息设置到SecurityContext中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(obj,null,null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行,到达目标方法
filterChain.doFilter(request,response);
}
}
7)退出登录
package com.hl.springsecurity01.web;
import com.hl.springsecurity01.domain.R;
import com.hl.springsecurity01.service.LoginService;
import com.hl.springsecurity01.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.security.sasl.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("/login")
public R login(String username, String password) throws AuthenticationException {
//调用service
return loginService.login(username, password);
}
@RequestMapping("/logout1")
public R logout(HttpServletRequest request) throws Exception {
String token = request.getHeader("token");
//解析token,得到用户id
Claims claims = JwtUtil.parseJWT(token);
Object object = claims.getSubject();
Long userId = Long.parseLong(object.toString());
//从redis中删除用户信息
redisTemplate.delete(userId);
//springsecurity上下文中清除用户信息
SecurityContextHolder.getContext().setAuthentication(null);
return R.ok();
}
}
8)权限控制
1. 开启权限拦截
@SpringBootApplication
@MapperScan(basePackages = "com.hl.springsecurity01.mapper")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Springsecurity01Application {
public static void main(String[] args) {
SpringApplication.run(Springsecurity01Application.class, args);
}
}
2.方法上添加拦截注解
@Controller
public class BasicController {
// http://127.0.0.1:8080/hello?name=lisi
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('user:list')")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
return "Hello " + name;
}
3、授权(模拟字符串授权)
UserDetailsService和UserDetails
/*
根据用户名查找用户对象
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名,到数据库表中,查找用户对象
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("user_name", username);
List<SysUser> list = sysUserService.list(queryWrapper);
//判断用户是否存在
LoginUser user = null;
if(list != null && list.size() > 0){
SysUser sysUser = list.get(0);
//授权
List<String> permissions = new ArrayList<>();
permissions.add("user:list");
permissions.add("user:add");
//封装数据到UserDetails接口实现类对象中
user = new LoginUser(sysUser,permissions);
}
return user;
}
@Data
public class LoginUser implements UserDetails {
private SysUser sysUser;
private List<String> permissions;
public LoginUser() {
}
public LoginUser(SysUser sysUser, List<String> permissions) {
this.sysUser = sysUser;
this.permissions = permissions;
}
//返回用户权限信息,返回权限列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
for (String permission : permissions) {
list.add(new SimpleGrantedAuthority(permission));
}
return list;
}
JwtAuthenticationInterceptor
package com.hl.springsecurity01.security;
import com.hl.springsecurity01.util.JwtUtil;
import com.mysql.cj.log.Log;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
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;
/*
创建token过滤器
*/
@Component
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("到达jwt过滤器.....");
//获取请求头中的token
String token = request.getHeader("token");
if(token == null){
// throw new RuntimeException("token不能为空!");
System.out.println("token为空!");
//放行,到usernamePasswordtoken
filterChain.doFilter(request,response);
return;
}
//校验token是否合法
Long userId = null;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = Long.parseLong(claims.getSubject());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token 不合法");
}
//判断用户是否登录成功,服务端是否存在该用户信息
Object obj = redisTemplate.opsForValue().get(userId);
if(obj == null){
System.out.println("用户未登录");
throw new RuntimeException("用户未登录!");
}
LoginUser user = (LoginUser)obj;
//将登录成功的用户信息设置到SecurityContext中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(obj,null,user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行,到达目标方法
filterChain.doFilter(request,response);
}
}
/**
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
@Controller
public class BasicController {
// http://127.0.0.1:8080/hello?name=lisi
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('user:list')")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
return "Hello " + name;
}
// http://127.0.0.1:8080/hello?name=lisi
@RequestMapping("/hello2")
@PreAuthorize("hasAuthority('user:hello')")
@ResponseBody
public String hello2(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
return "Hello " + name;
}
hello可以访问,hello2无法访问。
4、授权(连接数据库表)
public interface SysUserMapper extends BaseMapper<SysUser> {
@Select(value = "select sys_menu.perms " +
"from sys_menu " +
"join sys_role_menu on sys_menu.menu_id = sys_role_menu.menu_id " +
"join sys_user_role on sys_role_menu.role_id = sys_user_role.role_id " +
"where sys_user_role.user_id = #{id} and perms is not null and perms !=''")
public List<String> findPermissionsByUserId(Long userId);
}
package com.hl.springsecurity01.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hl.springsecurity01.domain.SysUser;
import com.hl.springsecurity01.service.SysUserService;
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.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
/*
根据用户名查找用户对象
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名,到数据库表中,查找用户对象
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("user_name", username);
List<SysUser> list = sysUserService.list(queryWrapper);
//判断用户是否存在
LoginUser user = null;
if(list != null && list.size() > 0){
SysUser sysUser = list.get(0);
//授权
// List<String> permissions = new ArrayList<>();
// permissions.add("user:list");
// permissions.add("user:add");
List<String> permissions = sysUserService.findPermissionsByUserId(sysUser.getId());
//封装数据到UserDetails接口实现类对象中
user = new LoginUser(sysUser,permissions);
}
return user;
}
}
9)权限控制相关的注解
在Spring Security中,hasRole和hasAuthority都可以用来控制用户的访问权限,但它们有一些细微的差别。
hasRole方法是基于角色进行访问控制的。它检查用户是否有指定的角色,并且这些角色以"ROLE_"前缀作为前缀(例如"ROLE_ADMIN")。
hasAuthority方法是基于权限进行访问控制的。它检查用户是否有指定的权限,并且这些权限没有前缀。
因此,使用hasRole方法需要在用户的角色名称前添加"ROLE_"前缀,而使用hasAuthority方法不需要这样做。
例如,假设用户有一个角色为"ADMIN"和一个权限为"VIEW_REPORTS",可以使用以下方式控制用户对页面的访问权限:
.antMatchers("/admin/").hasRole("ADMIN") .antMatchers("/reports/").hasAuthority("VIEW_REPORTS") 在这个例子中,只有具有"ROLE_ADMIN"角色的用户才能访问/admin/路径下的页面,而具有"VIEW_REPORTS"权限的用户才能访问/reports/路径下的页面。
@PreAuthorize("hasAuthority('system:user:list')") 特定的菜单权限
@PreAuthorize("hasAnyAuthority('system:user:list','system:user:add')") 多个菜单权限只要有一个就可以访问
@PreAuthorize("hasRole('admin')")
@PreAuthorize("hasAnyRole('admin','comm')")
-- 根据用户,查询角色列表
select sys_role.role_key
from sys_role join sys_user_role
on sys_role.role_id = sys_user_role.role_id
where sys_user_role.user_id = 2
union all
select sys_menu.perms
from sys_menu
join sys_role_menu on sys_menu.menu_id = sys_role_menu.menu_id
join sys_user_role on sys_role_menu.role_id = sys_user_role.role_id
where sys_user_role.user_id = 2 and perms is not null and perms !=''
ROLE_common
system:user:list
system:role:list
system:menu:list
system:dept:list
system:post:list