SpringSecurity的使用与步骤

1、SpringSecurity流程图

2、导入坐标

<!-- spring-boot-starter-security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--        jjwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

3.1、SpringSecurity的配置类

package com.springsecuritylearning.config;

import com.springsecuritylearning.filter.DoFilterInternal;
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.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
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DoFilterInternal filterInternal;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //关闭csrf
            .csrf().disable()
            //不通过的Session获取SecurityContext
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            //对于该接口放行
            .antMatchers("/auth/user/**").permitAll()
            //其余接口拦截
            .anyRequest().authenticated();
        http
            .addFilterBefore(filterInternal, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

3.2、启动类添加

@EnableGlobalMethodSecurity(prePostEnabled = true)

4、根据流程图可以看出用户登录接口,将用户名和密码进行提交,所以先写登录接口(流程图第1步)

/**
 * 登录验证
 */
@RestController
@RequestMapping("/auth/user")
@CrossOrigin
@Slf4j
public class UserController {

    @Autowired
    private UserServiceImpl userService;

    /**
     * 用户登录
     * @param user
     * @return
     */
    @PostMapping("/login")
    public R login(User user){
        System.out.println(user);
        String token = userService.login(user);
        HashMap<String,String> map=new HashMap<>();
        map.put("token",token);
        return R.ok(map).setCode(200);
    }
}

5、创建login接口的实现类(流程图第2、3步,第4步自动完成,最后获取到Authentication为第9步)

package com.xuechengplusauth.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuechengplusauth.domain.User;
import com.xuechengplusauth.mapper.UserMapper;
import com.xuechengplusauth.security.LoginUser;
import com.xuechengplusauth.service.UserService;
import com.xuechengpluscommon.utils.JwtUtils;
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 org.springframework.util.ObjectUtils;

/**
* @author 卿十三
* @description 针对表【xc_user】的数据库操作Service实现
* @createDate 2023-02-08 14:48:36
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private AuthenticationManager authenticationManager;
    @Override
    public String login(User user) {
        //将请求的信息封装到Authentication,实现类为UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        //判断Authentication是否通过认证,如果为空则没有通过PasswordEncoder的认证,说明账号密码错误
        if(ObjectUtils.isEmpty(authenticate)){
            throw new RuntimeException("未通过认证");
        }
        //从Authentication获取信息
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        //从loginUser类获取User信息
        User loginUserUser = loginUser.getUser();
        String id = loginUserUser.getId();
        //将用户Id保存到redis
        redisTemplate.opsForValue().set(id,loginUser);
        //将用户Id转成JWT然后返回
        String token = JwtUtils.createToken(id);
        return token;
    }
}

6、经过自动认证之后来到UserDetailsService进行手动填充信息,首先创建LoginUser类实现UserDetails接口,用来作为loadUserByUsername类的返回值(流程图5、6、7、8步)

package com.xuechengplusauth.security;

import com.alibaba.fastjson.annotation.JSONField;
import com.xuechengplusauth.domain.User;
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;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {

    //存储用户信息
    private User user;

    //存储权限信息
    private List<String> authList;

    //优化getAuthorities类,避免多次重复调用
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    public LoginUser(User user,List<String> authList){
        this.user=user;
        this.authList=authList;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }else{
            authorities=new ArrayList<>();
            for (String s : authList) {
                SimpleGrantedAuthority simpleGrantedAuthority=new SimpleGrantedAuthority(s);
                authorities.add(simpleGrantedAuthority);
            }
            return authorities;
        }
    }
    //用户密码,通过user.getPassword()获取的密码来通过PassWordEncoder加密后和数据库的密码进行校验
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    //用户账号
    @Override
    public String getUsername() {
        return user.getUsername();
    }
    //返回true意味着账号未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //返回true意味着账号未锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //返回true意味着凭证未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    //返回true意味着可以使用
    @Override
    public boolean isEnabled() {
        return true;
    }
}

7、创建UserDetailsImpl类实现UserDetailsService接口,通过重写loadUserByUsername方法实现自定义校验(流程图5、6、7、8步)

package com.xuechengplusauth.security.Impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuechengplusauth.domain.Menu;
import com.xuechengplusauth.domain.Permission;
import com.xuechengplusauth.domain.User;
import com.xuechengplusauth.domain.UserRole;
import com.xuechengplusauth.mapper.MenuMapper;
import com.xuechengplusauth.mapper.PermissionMapper;
import com.xuechengplusauth.mapper.UserMapper;
import com.xuechengplusauth.mapper.UserRoleMapper;
import com.xuechengplusauth.security.LoginUser;
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 org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private MenuMapper menuMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据输入的账号username从数据库查询到用户
        LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,username);
        //获取查到的用户
        User user = userMapper.selectOne(queryWrapper);
        //如果为空用户不存在
        if(ObjectUtils.isEmpty(user)){
            throw new RuntimeException("用户不存在");
        }

        //从数据库查询该用户所拥有的权限信息
            //创建权限集合,用来存储权限
        List<String> authList=new ArrayList<>();
        String id = user.getId();
        LambdaQueryWrapper<UserRole> queryWrapper1=new LambdaQueryWrapper<>();
        queryWrapper1.eq(UserRole::getUserId,id);
        UserRole userRole = userRoleMapper.selectOne(queryWrapper1);
        String roleId = userRole.getRoleId();
        LambdaQueryWrapper<Permission> queryWrapper2=new LambdaQueryWrapper<>();
        queryWrapper2.eq(Permission::getRoleId,roleId);
        List<Permission> permissions = permissionMapper.selectList(queryWrapper2);
        for (Permission permission : permissions) {
            String menuId = permission.getMenuId();
            Menu menu = menuMapper.selectById(menuId);
            String code = menu.getCode();
            authList.add(code);
        }
        //将用户信息和权限信息封装到loginUser中
        LoginUser loginUser=new LoginUser(user,authList);
        
        
        //返回loginUser
        return loginUser;
    }
}

8、编写拦截器将Authentication保存到上下文中,通过继承OncePerRequestFilter类实现doFilterInternal接口实现拦截器的功能(流程图第10步)

package com.xuechengplusauth.filter;

import com.xuechengplusauth.security.LoginUser;
import com.xuechengpluscommon.utils.JwtUtils;
import org.apache.commons.lang.StringUtils;
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.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
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.Collection;

@Component
public class DoFilterInternal extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头的token信息
        String token = request.getHeader("token");
        //如果为空,则放行
        if(StringUtils.isEmpty(token)){
            filterChain.doFilter(request,response);
            return;
        }
        //JWT进行解密获取Token信息
        String tokenInfo = JwtUtils.getTokenInfo(token);
        //从数据库查询token信息中的用户
        LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(tokenInfo);
        //如果用户存在说明已经过认证,如果不存在,说明未经过认证
        if(ObjectUtils.isEmpty(loginUser)){
            throw new RuntimeException("用户不存在");
        }
        //用户已经进行认证的前提下,获取UserDetails的信息,从其中获取认证用户的权限和信息
        Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
        //通过SecurityContextHolder.getContext().setAuthentication方法将Authentication保存到上下文
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
            =new UsernamePasswordAuthenticationToken(loginUser,null,authorities);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(request,response);
    }
}

9、测试登录

设置token未空,在Security配类开启登录接口的放行,设置JWT的存活时间和Redis的存活时间为一小时

返回值为token

10、测试其他模块,该模块已导入SpringSecurity模块的包

  • 带有Token进行接口测试(成功访问)

  • 不带Token进行接口测试(禁止访问)

11、权限功能测试

  • 启动类添加注解

@EnableGlobalMethodSecurity(prePostEnabled = true)
  • 在Controller接口添加注解@PreAuthorize


    @RequestMapping("/test1")
    @PreAuthorize("hasAuthority('p1')")
    public String r(){
        return "访问资源R1";
    }

    @RequestMapping("/test2")
    @PreAuthorize("hasAuthority('p2')")
    public String test(){
        return "访问资源test";
    }
  • 添加三种权限p1,p2,p3

authList.add("p1");
authList.add("p2");
authList.add("p3");

获取当前用户的权限信息

xc_sysmanager
xc_sysmanager_user
xc_sysmanager_user_add
xc_sysmanager_user_edit
xc_sysmanager_user_view
xc_sysmanager_user_delete
xc_sysmanager_doc
xc_sysmanager_log
xc_teachmanager_course
xc_teachmanager_course_add
xc_teachmanager_course_base
p1
p2
p3

测试p1,p2接口访问(成功访问)

删除P1,2,p3权限,当前用户拥有的权限

xc_sysmanager
xc_sysmanager_user
xc_sysmanager_user_add
xc_sysmanager_user_edit
xc_sysmanager_user_view
xc_sysmanager_user_delete
xc_sysmanager_doc
xc_sysmanager_log
xc_teachmanager_course
xc_teachmanager_course_add
xc_teachmanager_course_base

测试接口(访问失败)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值