springsecurity02

发布于:2025-07-05 ⋅ 阅读:(19) ⋅ 点赞:(0)

提前打开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