渐进式学习springboot security

1.装箱即用的spring security

加入依赖,加个配置即可——应用场景:eureka server,hystrix dashboard,springboot admin等,用于增加登录验证(基于cookie:jssessionId实现用户session的,不能用postman等工具来测试)。该应用场景效果基本nginx配置账号密码验证差不多

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
spring:
  security:
    user:
      name: jwolf
      password: 123456

 

2.追加几个内存用户并配置其访问权限


import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;


@EnableWebSecurity
public class MemoryUserSecurityConfig extends WebSecurityConfigurerAdapter {

  //用户认证
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //内存里面初始化几个用户
    auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder())
            //添加用户,密码,角色
            .withUser("zs").password("123456").roles("AAA")
            //链式编程
            .and()
            .withUser("ls").password("123456").roles("BBB")
            .and()
            .withUser("ww").password("123456").roles("CCC", "primary").authorities("PPP");//PPP权限


  }


  //用户授权 ant风格的path
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/").permitAll() //应用首页所以用户都可以访问,完全无限制
            .antMatchers("/test-user/addUs*").hasRole("BBB") // 允许 AAA 角色访问
            .antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") //允许有"PPP"权限的访问
            .antMatchers("/test-user/updateUser").hasAnyRole("AAA", "BBB", "CCC")
            .antMatchers("/test-user/findAllUsers").permitAll()
            .anyRequest().authenticated() //其它path都有登录才能访问
            .and()
            .formLogin();//指定支持基于表单的身份验证,会暴露出/login /logout等端点,默认登录成功调向登录前的,可以自定义登录页面登录成功和失败重定向的url,例如.formLogin().loginPage("/myLogin").successForwardUrl("/xxxx").failureForwardUrl("/yyy")
 

       
  }

  private class MyPasswordEncoder implements org.springframework.security.crypto.password.PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
      return charSequence.toString(); //用户输入密码处理
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
      return s.equals(charSequence.toString());//与security上下文存储的用户密码比对
    }



  }
}
@RestController
@RequestMapping("/test-user")
public class TestUserController {

    @RequestMapping("/addUser")
    String addUser() {
        return "这是添加用户!!!";
    }

    @RequestMapping("/deleteUser")
    String deleteUser() {
        return "这是删除用户!!!";
    }

    @RequestMapping("/updateUser")
    String updateUser() {
        return "这是修改用户!!!";
    }

    @RequestMapping("/findAllUsers")
    String findAllUsers() {
        return "这是查询用户!!!";
    }
}

3.从DB获取用户信息进行权限控制

 配置类主要注入bean UserDetailsService

@Configuration
public class DBUserSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private UserDetailsService userDetailsService;

     //这里从数据库中读取数据到spring security上下文
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
 
    }


    //用户授权 ant风格的path
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll() //应用首页所以用户都可以访问,完全无限制
                .antMatchers("/test-user/addUs*").hasRole("BBB") // 允许 AAA 角色访问
                .antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") //允许有"PPP"权限的访问
                .antMatchers("/test-user/updateUser").hasAnyRole("AAA", "BBB", "CCC")
                .antMatchers("/test-user/findAllUsers").permitAll()
                .anyRequest().authenticated() //其它path都有登录才能访问
                .and()
                .formLogin();//指定支持基于表单的身份验证,会暴露出/login /logout等端点

    }


    @Bean
    public  PasswordEncoder getPasswordEncoder(){
       return new PasswordEncoder() {
            @Override
            public String encode(CharSequence password) {
                return password.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                String encodeStr = encode(rawPassword);
                return encodedPassword.equalsIgnoreCase(encodeStr);
            }
        };
    }
}

需要实现spring security的 UserDetailsService 接口,下面基本就是常规的业务类型代码了,这里注入的UserMapper

@Service
public class SecurityUserServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SecurityUser user = userMapper.selectByUsername(username);
        if (user == null) {
            throw new BadCredentialsException("用户名或密码错误");
        }
        return user;
    }
}

entity是这样的,需要实现spring secury的UserDetails

import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Getter
@Setter
public class SecurityUser implements UserDetails {
    private static final long serialVersionUID=1L;
    private long id;
    private String password;
    private String username;
    private List<GrantedAuthority> anthorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.anthorities;
    }

    //账户是否未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
 
    //账户是否未锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
 
    //帐户密码是否未过期,一般有的密码要求性高的系统会使用到,比较每隔一段时间就要求用户重置密码
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
 
    //账户是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }
}

userMapper.xml是这样的


    <resultMap id="userResultMap" type="com.construn.vehicle.user.entity.SecurityUser">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <collection property="anthorities" ofType="org.springframework.security.core.authority.SimpleGrantedAuthority">
            <result column="rolename" property="role"/>
        </collection>
    </resultMap>

    <select id="selectByUsername" resultMap="userResultMap">
        select a.id,a.username,a.password,b.rolename from t_user a  left join t_role b on a.role_id=b.id
        where a.username=#{username}
    </select>

4.关于role 与authority区别及权限注解的开启

使用权限注解需要启动类开启,该注解有几个参数,默认都是关的@EnableGlobalMethodSecurity(prePostEnabled = true),@DenyAll @RolesAllowed({"USER", "ADMIN"}) @PermitAll 等开关需要通过jsr250Enabled = true来开启@EnableGlobalMethodSecurity(prePostEnabled = true,jsr250Enabled = true)

核心配置类DBUserSecurityConfig的.antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") 改为权限配置应该是接口使用注解@PreAuthorize("hasAnyAuthority('PPP')"),如果数据存的rolename为ROLE_XXX,或存入SimpleGrantedAuthority的rolename有ROLE_前缀,就应该使用@PreAuthorize("hasAnyRole('PPP')"),role与authority区别参考https://www.cnblogs.com/Rocky_/p/11799772.html

5.整合JWT

增加jwt依赖

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

原生的security认证信息通过cookie-session保存会话session,用户客户端或服务端重启都会导致重新登录,多数情况需要整合jwt,这里较step3多了两个filter,一个用于登录成功将jwt token写到浏览器,一个用于访问资源时解析jwt token放入到security 认证上下文,免密加密用的security亲生的BCryptPasswordEncoder


import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.ibatis.javassist.bytecode.ByteArray;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import sun.security.provider.MD5;

import java.util.ArrayList;
import java.util.Collection;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
    }




    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll() //应用首页所以用户都可以访问,完全无限制
                .antMatchers("/test-user/addUs*").hasRole("BBB") // 允许 AAA 角色访问
                //.antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") //允许有"PPP"权限的访问
                .antMatchers("/test-user/updateUser").hasAnyRole("AAA", "BBB", "CCC")
                .antMatchers("/test-user/findAllUsers").permitAll()
                .anyRequest().authenticated() //其它path都有登录才能访问
                .and().csrf().disable()
                .formLogin()//指定支持基于表单的身份验证,会暴露出/login /logout等端点
                .and()
                .addFilter(new JWTLoginFilter(authenticationManager()))
                .addFilter(new JWTAuthenticationFilter(authenticationManager()));
        
    }



    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence password) {
                //用户注册调用该方法进行密码加密
                // BCryptPasswordEncoder对相同字符串每次加密都结果都不一样,但matches()时都能比较成功,具有更高安全性
                return new BCryptPasswordEncoder().encode(password);
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                //用户登录将用户表单传入的rawPassword与数据库的密码比对。
                return new BCryptPasswordEncoder().matches(rawPassword,encodedPassword);
            }
        };
    }

}

两个过滤器



import com.alibaba.fastjson.JSON;
import com.construn.vehicle.common.base.entity.ResultEntity;
import com.construn.vehicle.user.entity.SecurityUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * 验证用户名密码正确后,生成一个token,并将token返回给客户端
 * 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 ,
 * attemptAuthentication:接收并解析用户凭证。
 * successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。
 */
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password, null));

    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {
        Claims claims = Jwts.claims();
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        claims.put("userId", user.getId());
        claims.put("role", auth.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList()));
        String token = Jwts.builder()
                .setClaims(claims)
                .setSubject(auth.getName())
                .setExpiration(new Date(System.currentTimeMillis() + 600 * 1000))
                .signWith(SignatureAlgorithm.HS512, "jwolf").compact();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        ResultEntity result = ResultEntity.success(token, "登录成功");
        PrintWriter out;
        try {
            out = response.getWriter();
            out.print(JSON.toJSONString(result));
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 从request中获取token并解析,拿到用户信息,放置到SecurityContextHolder,这样便完成了springsecurity和jwt的整合。
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }
        Claims claims = Jwts.parser().setSigningKey("jwolf").parseClaimsJws(token.replace("Bearer ", ""))
                .getBody();
        List<String> roles = claims.get("role", List.class);
        List<SimpleGrantedAuthority> auth = roles.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());
        //如果controller还需要userId等信息其它更多信息,可以第一个参数传入甚至整个claims,最好不要再次从header里获取token二次解析出用户信息,
        //然后在controller直接SecurityContextHolder.getContext().getAuthentication()获取;
        UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken( claims.getSubject(), null, auth);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);


    }

}

其中登录过滤器可以优化,抽出JWT工具类,并可以不用该过滤器,使用登录成功处理器AuthenticationSuccessHandler进行替换,登录失败及登陆security也提供了类似的接口可以自己实现然后在security核心配置即可如登录成功: .successHandler(userLoginSuccessHandler)  参考https://www.tuicool.com/articles/Q7fU73E

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值