栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Security基础使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Security基础使用

前言

学习Spring Security 的认证和授权。本文主要介绍在前后端分离开发的情况下的使用。

一、Spring Security 的依赖
		
			org.springframework.boot
			spring-boot-starter-security
		

二、新建控制器类
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

	@GetMapping("/hello")
	public Object sayHello(){
		return "hello world";
	}


}

三、访问请求

项目启动成功后, 用浏览器访问 http://localhost:8080/hello
正常情况下浏览器会打印出来 “hello world” 字符串。但是我们因为我们引入了 Spring Security 依赖。浏览器会自动跳转到 http://localhost:8080/login 。如下图:

四、登录流程


思路分析
登录
1.准备登录接口,参数是账号和密码。调用ProviderManager的方法进行验证,。如果验证通过把用户信息存入Redis,键为id。根据id生成JWT响应给前端。
2.自定义userDetailsService类的实现类.在这个类中查询数据库。
校验(即如何获取用户信息思路)
1.创建JWT解析过滤器
获取token
解析token。获取id
根据id在Redis中获取用户信息
用户信息存入在SecurityContextHolder中。

五、完善依赖和配置
		
			org.springframework.boot
			spring-boot-starter-security
		

		
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
 		
            com.alibaba
            fastjson
            1.2.58
        
		
		    io.jsonwebtoken
		    jjwt
		    0.9.1
		

整合Redis
spring.redis.database=
spring.redis.host=
spring.redis.port=6379
spring.redis.password=

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 设置key的序列化方式
        template.setKeySerializer(RedisSerializer.string());
        // 设置value的序列化方式
        template.setValueSerializer(RedisSerializer.json());
        // 设置hash的key的序列化方式
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置hash的value的序列化方式
        template.setHashValueSerializer(RedisSerializer.json());

        template.afterPropertiesSet();
        return template;
    }

}
@Data
public class Result {
    
    private int code;
    
    private String msg;

    
    private Object data;

    public Result(){
    }

    public Result(int code, String msg,Object data){
        this.code=code;
        this.data=data;
        this.msg=msg;
    }
    public Result(int code, String msg){
        this.code=code;
        this.msg=msg;
    }
    public Result(int code, Object date){
        this.code=code;
        this.data=date;
    }
    public Result(int code){
        this.code=code;
    }
}
public class JwtUtils {

    
    private static final int TOKEN_TIME_OUT = 3_600*24*7;
    
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjjZiY20NjIxDM3kM2NhZU0ZTgzjMjYyN2I0ZjY";

    
    public static String getToken(Integer userId){
        long currentTime = System.currentTimeMillis();
        //头信息
       return Jwts.builder().setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
        //载荷
        .claim("userId",userId)
         //过期时间戳
        .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))
        //jwt编号:随机产生
        .setId(UUID.randomUUID().toString())
         //签名
        .signWith(SignatureAlgorithm.HS256, TOKEN_ENCRY_KEY)
        .compact();
    }
    
    private static Jws getJws(String token) {
        return Jwts.parser()
                .setSigningKey(TOKEN_ENCRY_KEY)
                .parseClaimsJws(token);
    }
    
    public static Claims getClaims(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }


    
    public static Integer checkToken(String token) {
        try {
            Claims claims = getClaims(token);
            if(claims==null){
                throw new RuntimeException("token解析失败");
            }
            return (Integer) claims.get("userId");
        } catch (ExpiredJwtException ex) {
            throw new RuntimeException("token已经失效");
        }catch (Exception e){
            throw new RuntimeException("token解析失败");
        }
    }

}
六、认证的代码实现

自定义userDetailsService类的实现类.在这个类中查询数据库。把查询到的用户信息封装成UserDetails类

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        User user = userMapper.selectOne(new QueryWrapper().eq("qq", username));
        if (Objects.isNull(user)){
            throw  new RuntimeException("账号错误或者密码错误");
        }
        //查询用户授权信息(待完成)
        return new LoginUser(user);
    }
}

返回结果是UserDetails 类。这个是接口,需要实现。

@Data
public class LoginUser implements UserDetails {
 	private User user;
    public LoginUser(User user){
        this.user=user;
    }
    @Override
    public Collection 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;
    }
}

Spring Security 默认有密码加密格式。这里需要替换成BCryptPasswordEncoder。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

这个对象可以对密码进行加密。可以对明文和加密密码进行校验。

定义登录接口
配置类中加入AuthenticationManager (内部调用UserDetailsService 的实现类,会自动校验账号和密码)和 重写configure(登录接口放行)

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 @Bean
    public PasswordEncoder  passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // CSRF禁用
                // 从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 过滤请求
                .and()
                .authorizeRequests()
                //允许匿名访问
                .antMatchers("/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }

  
}


 @PostMapping(path = "/login")
    public Result login(@RequestBody User user){
        //获取前端输入的qq和密码
        String qq = user.getQq();
        String password = user.getPassword();
        //把前端账号和密码封装成Authentication
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(qq,password);
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if (Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        //用户信息保存在Redis
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        User u = loginUser.getUser();
        Integer id = u.getId();
        u.setPassword(null);
        u.setCreateTime(null);
        u.setUpdateTime(null);
        redisTemplate.opsForValue().set("login:"+id,u);
        //认证通过,根据userId生成token。

        String token = JwtUtils.getToken(id);
        return new Result(200,"登录成功",token);

    }
七、校验过滤器的代码实现(解析token)
@Component
public class JwtTokenFilter 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.isBlank(token)){
            //放行
            filterChain.doFilter(request,response);
            return;
        }
        //解析token
        Integer id=0;
        try {
             id = JwtUtils.checkToken(token);

        }catch (Exception e){
            // 将错误信息封装在request中
            request.setAttribute("exception", e);
            // 请求转发
            request.getRequestDispatcher("/filterGlobalException").forward(request, response);
        }
        //从Redis中获取用户信息
        User user = (User) redisTemplate.opsForValue().get("login:" + id);
        if (Objects.isNull(user)){
            // 将错误信息封装在request中
            request.setAttribute("exception", new Exception("用户未登录!"));
            // 请求转发
            request.getRequestDispatcher("/filterGlobalException").forward(request, response);
        }
        // 用户信息存入在SecurityContextHolder中。
        UsernamePasswordAuthenticationToken authenticationToken=
                new UsernamePasswordAuthenticationToken(user,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request,response);
    }
}

Spring Security有一系列的过滤器。 需要配置该过滤器的顺序

 @Autowired
    private JwtTokenFilter jwtTokenFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // CSRF禁用
                // 从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 过滤请求
                .and()
                .authorizeRequests()
                //允许匿名访问
                .antMatchers("/login")
                .anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
                //配置过滤器
        http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
八、用户身份权限(简单版用户角色String role)
@Service
public class UserDetailsServiceImpl implements UserDetailsService {//452014375
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        User user = userMapper.selectOne(new QueryWrapper().eq("qq", username));
        if (Objects.isNull(user)){
            throw new RuntimeException("账号或者密码错误!");
        }
        //查询用户授权信息(user类中有一个String role字段表示用户身份)
        return new LoginUser(user);
    }
}

这一步增加了根据user对象的role属性,getAuthorities()方法封装了用户权限信息。

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;
    public LoginUser(User user){
        this.user=user;
    }

    @Override
    @JsonIgnore
    public Collection getAuthorities() {
        List list=new ArrayList<>();
        list.add(new SimpleGrantedAuthority(user.getRole()));
        return list;
    }

    @Override
    @JsonIgnore
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    @JsonIgnore
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isEnabled() {
        return true;
    }
}

这一步 UsernamePasswordAuthenticationToken authenticationToken=
new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
第三个参数是用户的权限。

@Component
public class JwtTokenFilter 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.isBlank(token)){
         //放行
         filterChain.doFilter(request,response);
         return;
     }
     //解析token
     Integer id=0;
     try {
          id = JwtUtils.checkToken(token);

     }catch (Exception e){
         // 将错误信息封装在request中
         request.setAttribute("exception", e);
         // 请求转发
         request.getRequestDispatcher("/filterGlobalException").forward(request, response);
     }
     //从Redis中获取用户信息
     LoginUser user = (LoginUser) redisTemplate.opsForValue().get("login:" + id);
     if (Objects.isNull(user)){
         // 将错误信息封装在request中
         request.setAttribute("exception", new Exception("用户未登录!"));
         // 请求转发
         request.getRequestDispatcher("/filterGlobalException").forward(request, response);

     }
     // 用户信息存入在SecurityContextHolder中。
     UsernamePasswordAuthenticationToken authenticationToken=
             new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
     SecurityContextHolder.getContext().setAuthentication(authenticationToken);
     //放行
     filterChain.doFilter(request,response);
 }
}

@PreAuthorize(“hasAuthority(‘test’)”)注解。如果用户角色是test,那么才可以访问该接口。

    @RequestMapping(path = "/hello", method = RequestMethod.POST)
    @PreAuthorize("hasAuthority('test')")
    public String hello() {
        return "hello";
    }
总结

第八章用户权限的区分管理是很简单的,单纯依赖User对象的role(角色)字段进行判断。完整的权限管理参考第九章。
完整的User类

@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String qq;
    private String password;
    private String idCard;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    //用户角色
    private String role;
}
九、 RBAC权限模型

RBAC权限模型
  在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。(如下图)

建立以上五个表,可以更加细腻,精准的管理用户权限。比单纯的在页面进行是否有某功能按键展现的判断更加安全。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/888427.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号