SpringSecurity

1.网页

创建项目

  • 启动项目

  • 访问页面会被拦截,需要输入用户名和密码

  • 用户名:user

  • 密码:项目启动的时候,控制台生成的密码

  • 登录成功之后,会自动跳转到最后一次访问的url地址

介绍

认证:登录,让不让你登录

授权:登录之后,能访问什么

防止网络攻击

SpringSecurity的认证和授权是分开的,并且二者不会互相影响

自定义登录页面

页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
  <h1>登录</h1>
  <form action="/java_login" method="post">
    <p>
      <input type="text" placeholder="用户名" name="uname">
    </p>
    <p>
      <input type="password" placeholder="密码" name="pwd">
    </p>
    <p>
      <button>登录</button>
    </p>
  </form>
</body>
</html>

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http未登录不能访问
        //所有的请求,都必须是认证之后才能访问
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        //配置 表单请求
     http.formLogin()
                .loginPage("/loginpage.html")//配置登录的html页面
                .usernameParameter("uname")//页面表单提交的时候,用户名的name值
                .passwordParameter("pwd")//密码的name值
                .loginProcessingUrl("/java_login")//页面表单提交的地址,action的值
                .defaultSuccessUrl("/success.html",true)
                .permitAll();//以上配置 都允许 不登录访问


        //csrf防跨域伪造 关闭
        //http.cors().disable();
        http.csrf().disable();
    }

    //放过静态资源

    @Override
    public void configure(WebSecurity web) throws Exception {
        //绕开  security框架,能访问的地址
        web.ignoring().antMatchers("/css/**","/js/**","*.html");

    }
}

使用数据库的用户名登录

配置完成mybatisplus的代码

生成代码

写一个类,实现UserDetails 用来 存储 数据库查询出来的 用户名密码等数据

@NoArgsConstructor
public class LoginUser implements UserDetails {

    RgAdminUser adminUser;

    public LoginUser(RgAdminUser adminUser) {
        this.adminUser = adminUser;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //授权相关的,返回的这个集合,就是用户的角色集合或者权限标识集合
        //Security中 认证 和 授权 是完全独立分开的
        return null;
    }

    @Override
    public String getPassword() {
        //获取 数据库的密码
        return adminUser.getPassword();
    }

    @Override
    public String getUsername() {
        //获取用户名
        return adminUser.getUsername();
    }

    //一下 4个 方法,都必须 同时 返回true 才能认证成功
    @Override
    public boolean isAccountNonExpired() {
        //账号是否过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        //账号是否锁定
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        //凭证是否过期
        return true;
    }

    @Override
    public boolean isEnabled() {
        //是否启用
        return adminUser.getStatus() == 0;
    }
}

UserDetailService的实现类

@Service("unameUserDetailsService")
public class UnameUserDetailsServiceImpl implements UserDetailsService {
    @Resource
    RgAdminUserService adminUserService;

    //认证的方法中,会执行当前的方法,传入  用户名 需要我们返回一个UserDetails
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据username 查询 用户信息
        RgAdminUser adminUser = adminUserService.queryByUname(username);
        if (adminUser!=null){
            return new LoginUser(adminUser);
        }
        return null;
    }
}

{noop}123 不加密的密码.真实的密码前面,必须额外加上{noop}

密码加密

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        //指明算法,代替系统原有的算法
        //加入这行代码之后,密码,必须加密
        return new BCryptPasswordEncoder();
    }

加密之后,之前的{noop}123 已经不能登录了

必须使用加密的字符串 才能登录

修改密码接口

 @Override
    public void resetPassword(Integer uid, String password) {
        //重置密码
        //新密码----加密之后的
        String newpassword = passwordEncoder.encode(password);
        //更新代码
        LambdaUpdateWrapper<RgAdminUser> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(RgAdminUser::getPassword,newpassword);
        updateWrapper.eq(RgAdminUser::getUid,uid);
        update(updateWrapper);
    }

注销登录

        //http.logout().logoutUrl("/log_out");
        //http.logout().logoutUrl("/log_out").logoutSuccessUrl("/logout.html").permitAll();
        http.logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/log_out","GET"))
                .logoutSuccessUrl("/logout.html").permitAll();

静态资源

以后是前后端分离的,服务端不存静态资源

2.JSON

Security--JSON

放弃页面

<!-- fastjson2 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.51</version>
</dependency>

返回JSON数据

        http.formLogin()
                .usernameParameter("uname")//页面表单提交的时候,用户名的name值
                .passwordParameter("pwd")//密码的name值
                .loginProcessingUrl("/javasm_login")//页面表单提交的地址,action的值
                .successHandler((request, response, authentication) -> createJSON(response,authentication))
                .failureHandler((request, response, e) -> createJSON(response,e.getMessage()))
                .permitAll();//以上配置 都允许 不登录访问

未登录 返回json


http.exceptionHandling()
        .authenticationEntryPoint((request, response, e) -> createJSON(response,e.getMessage()));

退出登录返回json

//退出登录
http.logout()
        .logoutUrl("/logout")
        .logoutSuccessHandler((request, response, authentication) -> 
                createJSON(response,authentication));

获取用户信息

查询当前登录的用户信息

  @Override
    public RgAdminUser getMyUser() {
        //获取上下文对象
        SecurityContext context = SecurityContextHolder.getContext();
        //从上下文对象中,获取用户信息
        //Authentication  实际上的对象是 UsernamePasswordAuthenticationToken
        Authentication authentication = context.getAuthentication();
        //个人信息,是在principal属性中
        //getPrincipal  UserDetail类型, 实际的对象 LoginUser
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        //获得属性
        RgAdminUser adminUser = loginUser.getAdminUser();

        return adminUser;
    }

修改个人信息

    @Override
    public void updateMyUser(RgAdminUser adminUser) {
        //获取已经登录的用户信息,从Security中获取用户信息,没有进过数据库
        RgAdminUser myUser = getMyUser();
        //判断 用户 是不是想修改密码,是否加了密码参数
        if (!StringUtils.isEmpty(adminUser.getPassword())){
            String newpassword = passwordEncoder.encode(adminUser.getPassword());
            //对象中的密码,就是加密之后的密码了
            adminUser.setPassword(newpassword);
            //
            myUser.setPassword(newpassword);
        }
        //数据库更新,
        adminUser.setUid(myUser.getUid());
        updateById(adminUser);
        new Thread(()->{

            //更新 Security中的用户信息
            SecurityContext context = SecurityContextHolder.getContext();
            //因为 用户 传入的参数 adminuser 可能属性不全,
            //新的值一个一个的拿进来
            if (!StringUtils.isEmpty(adminUser.getUsername())){
                myUser.setUsername(adminUser.getUsername());
            }
            if (adminUser.getStatus()!=null){
                myUser.setStatus(adminUser.getStatus());
            }

            UserDetails userDetails = new LoginUser(myUser);
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails,
                    userDetails.getPassword(),
                    userDetails.getAuthorities());
            context.setAuthentication(authentication);
        }).start();
    }

JSON登录

请求接口的时候,传入json格式数据,登录

1创建一个JsonLoginFilter,接收json格式数据

2修改配置类,添加新的过滤器

public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
    Logger logger = LogManager.getLogger(JsonLoginFilter.class);

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response)
            throws AuthenticationException {
        //限定只能post方式登录
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("请求方式不对: " + request.getMethod());
        }
        //判断 获取的参数 是不是 json数据
        if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
            throw new AuthenticationServiceException("请求类型不是JSON: " + request.getMethod());
        }

        try {
            //如何 从request对象中 获取JSON字符串
            /*
            {
                "username":"xiaoming",
                "password":"456"
            }
             */
            String json = RequestBodyUtil.read(request);
            //json转成map
            Map<String,String> map = JSONObject.parseObject(json, Map.class);
            //从map中获取参数
            String username = map.get("username");
            String password = map.get("password");
            //如果参数是null,给空值
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }
            username = username.trim();
            //用户名密码存入token 临时存储
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            //添加ip信息
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        } catch (IOException e) {
            throw new AuthenticationServiceException("参数接收错误: " + e.getMessage());
        }

    }
}
public class RequestBodyUtil {

    //处理 request中的参数,转成 json字符串返回
    public static String read(HttpServletRequest request) throws IOException {
        //request对象的流信息
        ServletInputStream inputStream = request.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        //把字节流转成了 字符串流
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        //StringBuilder 存储参数
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = bufferedReader.readLine())!=null){
            sb.append(line).append("\n");
        }
        //把StringBuilder转成String字符串
        String json = sb.toString().trim();
        return json;
    }
}

修改配置文件

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder() {
        //指明算法,代替系统原有的算法
        //加入这行代码之后,密码,必须加密
        return new BCryptPasswordEncoder();
    }

    /************************JSON格式登录,配置,开始*************************************/
    @Bean
    public JsonLoginFilter jsonLoginFilter() throws Exception {
        //想在new对象的时候,添加一些配置,所以没有在类的内部加@Companet
        JsonLoginFilter filter = new JsonLoginFilter();
        //调用父类的认证方法,必不可少
        filter.setAuthenticationManager(authenticationManagerBean());
        //配置登录成功之后,返回什么json
        filter.setAuthenticationSuccessHandler((request, response, authentication) -> createJSON(response, authentication));
        //失败之后的显示
        filter.setAuthenticationFailureHandler((request, response, e) -> createJSON(response, e.getMessage()));
        //设置 访问的路径
        filter.setFilterProcessesUrl("/json_login");
        return filter;
    }

    //获取用户名密码之后,如何校验
    //这样从Spring容器中获取对象,如果有很多同类型的实现类,因为加了name属性,也不会报错
    @Resource(name = "unameUserDetailsService")
    UserDetailsService userDetailsService;

    @Bean
    public DaoAuthenticationProvider jsonProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //设置 查询用户信息 使用 哪个service
        authenticationProvider.setUserDetailsService(userDetailsService);
        //配置 加密的算法
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }

    /************************JSON格式登录,配置,结束*************************************/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http未登录不能访问
        //所有的请求,都必须是认证之后才能访问
        http.authorizeRequests()
                .antMatchers("/admin/user/**").permitAll()
                .anyRequest()
                .authenticated();
        //配置JSON格式登录 加过滤器,告诉Security 是哪个类型的过滤器
        http.addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(jsonProvider());//是用哪个认证方法


        //配置 表单请求
        http.formLogin()
                .usernameParameter("uname")//页面表单提交的时候,用户名的name值
                .passwordParameter("pwd")//密码的name值
                .loginProcessingUrl("/javasm_login")//页面表单提交的地址,action的值
                .successHandler((request, response, authentication) -> createJSON(response, authentication))
                .failureHandler((request, response, e) -> createJSON(response, e.getMessage()))
                .permitAll();//以上配置 都允许 不登录访问
        //未登录 返回json
        http.exceptionHandling()
                .authenticationEntryPoint((request, response, e) ->
                        createJSON(response, e.getMessage()));
        //退出登录
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler((request, response, authentication) ->
                        createJSON(response, "退出登录成功"));
        //csrf防跨域伪造 关闭
        http.csrf().disable();
    }

    //放过静态资源

    @Override
    public void configure(WebSecurity web) throws Exception {
        //绕开  security框架,能访问的地址
        web.ignoring().antMatchers("/css/**", "/js/**", "*.html");
    }

    private void createJSON(HttpServletResponse response, Object o) throws IOException {
        //想返回数据,向Response对象中 设置内容
        response.setContentType("application/json;charset=utf-8");
        //向浏览器 写出数据的对象
        PrintWriter writer = response.getWriter();
        //写什么json,内容是什么
        R r = R.ok(o);
        String json = JSON.toJSONString(r);
        writer.write(json);
        writer.flush();
        writer.close();
    }
}

图片验证码登录

引入依赖

<dependency>
  <groupId>com.github.penggle</groupId>
  <artifactId>kaptcha</artifactId>
  <version>2.3.2</version>
</dependency>

生成图片验证码

@RestController
@RequestMapping("/sec")
public class SecurityController {
    @Resource
    HttpServletResponse response;
    @Resource
    HttpSession session;

    //显示验证码
    @GetMapping("/img/code")
    public void getImageCode() throws IOException {
        Properties properties = new Properties();
        //配置图片宽高
        properties.setProperty("kaptcha.image.with","150");
        properties.setProperty("kaptcha.image.height","50");
        //图片上面 显示的 数字,随机数
        properties.setProperty("kaptcha.textproducer.char.string","0123456789");
        //验证码长度
        properties.setProperty("kaptcha.textproducer.char.length","4");
        //创建配置对象
        Config config = new Config(properties);
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        //创建图形对象
        kaptcha.setConfig(config);
        //生成图片
        String code = kaptcha.createText();
        BufferedImage image = kaptcha.createImage(code);
        //code存入session
        session.setAttribute("json_code",code);
        //设置response头信息,为图片
        response.setContentType("image/jpeg");
        //获取流信息
        ServletOutputStream outputStream = response.getOutputStream();
        ImageIO.write(image,"jpg",outputStream);
    }
}
public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
    
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response)
            throws AuthenticationException {
        
			.....
			String json = RequestBodyUtil.read(request);
            //json转成map
            Map<String,String> map = JSONObject.parseObject(json, Map.class);
            //从map中获取参数
            String username = map.get("username");
            String password = map.get("password");
            //参数中 拿到的
            String code = map.get("code");
            //校验code
            if (code == null){
                throw new AuthenticationServiceException("验证码为空");
            }else {
                HttpSession session = request.getSession();
                Object o = session.getAttribute("json_code");
                if (o == null){
                    throw new AuthenticationServiceException("验证码未生成,请生成验证码");
                }else {
                    String sessionCode = o.toString();
                    if (!code.equals(sessionCode)){
                        throw new AuthenticationServiceException("验证码错误");
                    }
                }
            }
			......
    }
}

手机号验证码登录

发送验证码

    @Resource
    RedisTemplate<String,Object> redisTemplate;
    @Resource
    SmsUtil smsUtil;
    @Override
    public void sendCode(String phone) {
        //获取随机的字符串
        String code = RandomUtil.getCode(4);
        //存入redis
        String key = String.format(RedisKeys.UserCodePhone,phone);
        redisTemplate.opsForValue().set(key,code,10, TimeUnit.HOURS);
        //发短信
        smsUtil.sendSms(phone,code);
    }

手机号验证码登录

PhoneLoginFilter

public class PhoneLoginFilter extends AbstractAuthenticationProcessingFilter {
    public PhoneLoginFilter() {
        //当前的登录方式,url地址和请求类型
        super(new AntPathRequestMatcher("/phone_login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //如果不是post方法请求,报错
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //判断 获取的参数 是不是 json数据
        if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            throw new AuthenticationServiceException("请求类型不是JSON: " + request.getContentType());
        }
        //把用户传入的json字符串 获取到
        String json = RequestBodyUtil.read(request);
        Map<String, String> map = JSONObject.parseObject(json, Map.class);
        //接收参数
        String phone = map.get("phone");
        String code = map.get("code");
        phone = phone == null ? "" : phone.trim();
        code = code == null ? "" : code.trim();
        //手机号验证码 封到一个token对象中
        PhoneAuthenticationToken token = new PhoneAuthenticationToken(phone,code);
        //补充详情
        setDetails(request,token);

        return this.getAuthenticationManager().authenticate(token);
    }

    protected void setDetails(HttpServletRequest request, PhoneAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }
}

PhoneAuthenticationToken

public class PhoneAuthenticationToken extends AbstractAuthenticationToken {
    private final Object principal;//手机号  &  用户详情
    private Object credentials;//验证码

    public PhoneAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

    public PhoneAuthenticationToken( Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {
        //权限列表
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

PhoneAuthenticationProvider

@Component
public class PhoneAuthenticationProvider implements AuthenticationProvider {
    @Resource
    RedisTemplate<String,Object> redisTemplate;
    @Resource(name = "phoneDetailService")
    UserDetailsService userDetailsService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //PhoneLoginFilter中 存入的信息
        PhoneAuthenticationToken token = (PhoneAuthenticationToken) authentication;
        String phone = (String) token.getPrincipal();//手机号
        String code = (String) token.getCredentials();//验证码
        //校验  验证码对不对
        String key = String.format(RedisKeys.UserCodePhone,phone);
        //redis中获取正确的验证码
        Object o = redisTemplate.opsForValue().get(key);
        if (o == null){
            throw new BadCredentialsException("验证码过期");
        }
        String relCode = (String) o;
        if (!relCode.equals(code)){
            throw new BadCredentialsException("验证码错误");
        }
        //代码运行到这里,说明 验证码 校验通过
        //根据 手机号,查询 用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
        //组装成新的 PhoneAuthenticationToken
        //(用户信息,随意,权限列表)
        PhoneAuthenticationToken authenticationToken = new PhoneAuthenticationToken(userDetails,phone,userDetails.getAuthorities());
        //详情  ip等等
        authenticationToken.setDetails(token.getDetails());
        return authenticationToken;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return PhoneAuthenticationToken.class.isAssignableFrom(aClass);
    }
}
@Service("phoneDetailService")
public class PhoneUserDetailsServiceImpl implements UserDetailsService {

    @Resource
    RgAdminUserService adminUserService;

    @Override
    public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
        RgAdminUser adminUser = adminUserService.queryByPhone(phone);
        if (adminUser!=null){
            return new LoginUser(adminUser);
        }
        return null;
    }
}

SecurityConfig

 @Bean
    public PhoneLoginFilter phoneLoginFilter() throws Exception {
        PhoneLoginFilter filter = new PhoneLoginFilter();
        filter.setAuthenticationManager(authenticationManagerBean());
        //登录成功页面
        filter.setAuthenticationSuccessHandler((request, response, authentication) -> createJSON(response, authentication));
        //登录失败页面
        filter.setAuthenticationFailureHandler((request, response, e) -> createJSON(response, e.getMessage()));
        //因为在PhoneLoginFilter中 已经设置过登录的路径,不再设置
        return filter;
    }

    @Resource
    PhoneAuthenticationProvider phoneAuthenticationProvider;
 @Override
    protected void configure(HttpSecurity http) throws Exception {
        ......
        http.addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(jsonProvider());//是用哪个认证方法
        //配置 手机号验证码登录
        http.addFilterAt(phoneLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(phoneAuthenticationProvider);

        ......
    }

Token登录

TokenLoginFilter

public class TokenLoginFilter extends AbstractAuthenticationProcessingFilter {
    public TokenLoginFilter(){
        super(new AntPathRequestMatcher("/token_login","POST"));
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //如果不是post方法请求,报错
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //token字符串,是在header中 传递过来的
        String tokenstr = request.getHeader(JWTUtil.Admin_Token);
        if (StringUtils.isEmpty(tokenstr)){
            throw new AuthenticationServiceException("Token_Null");
        }
        //如果有token  下一步
        TokenAuthenticationToken authenticationToken = new TokenAuthenticationToken(tokenstr,"");
        //PhoneAuthenticationToken t1 = new PhoneAuthenticationToken(tokenstr,"");
        setDetails(request,authenticationToken);
        return this.getAuthenticationManager().authenticate(authenticationToken);
    }

    protected void setDetails(HttpServletRequest request, TokenAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }
}

TokenAuthenticationToken

public class TokenAuthenticationToken extends AbstractAuthenticationToken {
    private final Object principal;//手机号  &  用户详情
    private Object credentials;//验证码

    public TokenAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

    public TokenAuthenticationToken( Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {
        //权限列表
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

TokenAutheticationProvider

@Component
public class TokenAuthenticationProvider implements AuthenticationProvider {

    @Resource(name = "uidDetailsService")
    UserDetailsService userDetailsService;
    @Resource
    HttpServletResponse response;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        TokenAuthenticationToken token = (TokenAuthenticationToken) authentication;
        //token字符串
        String tokenStr = (String) token.getPrincipal();
        //获取里面的uid
        String uid = JWTUtil.parse(tokenStr);
        //根据uid 查询 用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(uid);

        //token登录成功
        //生成新的token,把新的token 存入 header对象
        String newTokenStr = JWTUtil.generate(uid);
        //token字符串 放到header对象中
        response.setHeader(JWTUtil.Admin_Token,newTokenStr);
        //允许js处理
        response.setHeader("Access-Control-Expose-Headers",JWTUtil.Admin_Token);
        //把用户详情,和权限列表,再次封到TokenAuthenticationToken
        TokenAuthenticationToken authenticationToken = new TokenAuthenticationToken(userDetails, uid,userDetails.getAuthorities());
        //详情  ip等等
        authenticationToken.setDetails(token.getDetails());
        return authenticationToken;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return TokenAuthenticationToken.class.isAssignableFrom(aClass);
    }
}

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /************************Token登录,配置,开始*************************************/
    @Resource
    TokenAuthenticationProvider tokenAuthenticationProvider;

    @Bean
    public TokenLoginFilter tokenLoginFilter() throws Exception {
        TokenLoginFilter filter = new TokenLoginFilter();
        filter.setAuthenticationManager(authenticationManagerBean());
        //登录成功页面
        filter.setAuthenticationSuccessHandler((request, response, authentication) -> createJSON(response, authentication));
        //登录失败页面
        filter.setAuthenticationFailureHandler((request, response, e) -> {
            e.printStackTrace();
            createJSON(response, e.getMessage());
        });
        //因为在TokenLoginFilter中 已经设置过登录的路径,不再设置
        return filter;
    }

    /************************Token登录,配置,结束*************************************/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        //配置 token登录
        http.addFilterAt(tokenLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(tokenAuthenticationProvider);

}

跨域

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //配置类
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //支持所有的域名
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedHeader("*");
        //允许 token
        corsConfiguration.addAllowedHeader(JWTUtil.Admin_Token);
        //允许cookies
        corsConfiguration.setAllowCredentials(true);
        //配置类 注册到 跨域类
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsFilter(source);
    }
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //跨域
    @Resource
    CorsFilter corsFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //跨域 ,必须在过滤器链之前 配置
        //在UsernamePasswordAuthenticationFilter过滤器之前,添加一个过滤器corsFilter
        http.addFilterBefore(corsFilter,UsernamePasswordAuthenticationFilter.class);
        //配置JSON格式登录 加过滤器,告诉Security 是哪个类型的过滤器
        http.addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(jsonProvider());//是用哪个认证方法
        //配置 手机号验证码登录
        http.addFilterAt(phoneLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(phoneAuthenticationProvider);
        //配置 token登录
        http.addFilterAt(tokenLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(tokenAuthenticationProvider);

    }

    
}

3.授权

授权

认证和授权是分开的

设计一下现在的权限表结构

没有配置过权限的url地址,认证成功的用户都能访问,不做限制

角色

@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //初始化 权限标识集合
        List<GrantedAuthority> list = new ArrayList<>();
        //角色
        RgAdminRole role = adminUser.getRole();
        if (role!=null){
            //创建 权限对象
            //ROLE_super_admin
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+role.getCode());
            //添加到集合中
            list.add(authority);
        }
        //如果多角色,循环 添加到列表中
        return list;
    }

配置文件

   http.authorizeRequests()
                .antMatchers("/dynamic/test1","/dynamic/test2").hasRole("super_admin")//只有  super_admin的角色,才能访问当前的链接
                //.antMatchers("/dynamic/test2").hasRole("admin")//后设置的权限 覆盖了前面的权限
                .antMatchers("/dynamic/test3").hasAnyRole("test","super_admin")
                .antMatchers("/admin/user/**", "/sec/**").permitAll()
                .anyRequest()
                .authenticated();

角色继承

    @Bean
    public RoleHierarchy roleHierarchy(){
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        //设置  admin 的权限 大于 test
        hierarchy.setHierarchy("ROLE_admin > ROLE_test");
        return hierarchy;
    }

权限标识

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //初始化 权限标识集合
        List<GrantedAuthority> list = new ArrayList<>();
       
        //权限标识
        List<RgAdminAuthority> authorityList = adminUser.getAuthorityList();
        //循环,把用户的权限标识,添加到权限集合中
        authorityList.forEach(auth ->{
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(auth.getCode());
            list.add(authority);
        });
        return list;
    }
  http.authorizeRequests()
                .antMatchers("/dynamic/test1").hasAuthority("buss")
                .antMatchers("/dynamic/test2").hasAuthority("test")
                .antMatchers("/dynamic/test3").hasAnyAuthority("buss","test")
                .antMatchers("/admin/user/**", "/sec/**").permitAll()
                .anyRequest()
                .authenticated();

自定义403页面

	//自定义 403
        http.exceptionHandling().accessDeniedHandler((request,response,e)->{
            createJSON(response,"没有权限");
        });

注解

使用Security的注解,控制授权

@Configuration
//开启的是Security授权注解
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

@Secured

拥有什么角色的人可以访问

不支持授权码

//当前方法,允许 拥有super_admin和test角色的人访问,角色的继承在这里失效
@Secured({"ROLE_super_admin","ROLE_test"})
//未生效
@Secured({"buss"})

@PreAuthorize

在方法 执行之前 ,进行控制

 //当前的方法,允许 拥有 buss,test 权限标识的人访问
 @PreAuthorize("hasAnyAuthority('buss','test')")
//只允许 拥有 buss权限的用户访问
@PreAuthorize("hasAuthority('buss')")
//只允许 拥有 super_admin 角色的人访问
@PreAuthorize("hasRole('ROLE_super_admin 角色的人访问')")
//只允许  拥有  test 角色的人访问,  经过测试,角色的继承 生效了,admin也可以访问
@PreAuthorize("hasRole('ROLE_test')")

@PostAuthorize

在方法执行之后,返回之前,进行控制

使用这个注解,方法一定会被执行,但是 不一定能返回

//允许访问当前方法,但是只有super_admin角色 可以返回数据
@PostAuthorize("hasAnyRole('ROLE_super_admin')")

@PreAuthorize@PostAuthorize 都可以使用 角色 和 授权码进行控制

@PreFilter

用来过滤参数的,把不符合条件的参数过滤掉

//只能过滤集合类型的参数
@PreFilter("filterObject.likeNum > 40")
@GetMapping("/save/list")
public R test7(@RequestBody List<RgDynamic> list){
    logger.info(list);
    return R.ok(list);
}

@PostFilter

//过滤 返回值, 只能过滤 集合类型的返回值
@PostFilter("filterObject.likeNum < 40")
@GetMapping("/list")
public List<RgDynamic> test8(){
    List<RgDynamic> list = dynamicService.list();
    return list;
}

自定义权限认证

1:使用Security的权限列表,做判断

@Component("javasmAuthorize")
public class JavasmExpressAuthorize {
    Logger logger = LogManager.getLogger(JavasmExpressAuthorize.class);
    /**
     * //确保当前的类中,只有1个方法,返回值类型是布尔
     * @param code  权限标识
     * @return  布尔,如果返回true 允许用户继续访问方法,如果返回false,不允许用户访问,拦截了
     */
    public boolean f23(String code){
        //传入权限标识,方法内部,判断,当前的用户,是否拥有这个权限
        //获取当前登录的信息
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal!=null){
            LoginUser loginUser = (LoginUser) principal;
            //获取框架里的 权限列表
            Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
            //校验,权限列表中,是否包含 code
            //判断 集合中的 每个元素,是否包含code
            /*for (GrantedAuthority grantedAuthority : authorities){
                //ROLE_super_admin   buss
                String hasCOde = grantedAuthority.getAuthority();
                if (hasCOde.equals(code)){
                    return true;
                }
            }
            return false;*/
            return authorities.stream()
                    .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(code));
        }
        return false;
    }
}
 //@PreAuthorize("@处理的类在Spring容器中的id")通过id 找到对应的bean
    @PreAuthorize("@javasmAuthorize.f23('buss')")
    //过滤 返回值, 只能过滤 集合类型的返回值
    @PostFilter("filterObject.likeNum < 40")
    @GetMapping("/list")
    public List<RgDynamic> test8(){
        List<RgDynamic> list = dynamicService.list();
        return list;
    }

2:自己写判断的流程

@Component("javaPreAuthorize")
public class ZhangPreAuthorize {
    public boolean f1(String code){
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal!=null){
            //用户信息
            LoginUser loginUser = (LoginUser) principal;
            //获取 登录的用户信息,我们自己的用户对象
            RgAdminUser adminUser = loginUser.getAdminUser();
            //获取权限列表
            List<RgAdminAuthority> authorityList = adminUser.getAuthorityList();
            List<String> codeList = authorityList.stream().map(RgAdminAuthority::getCode).collect(Collectors.toList());
           /* List<String> codeList1 = new ArrayList<>();
            authorityList.forEach(authority ->{
                codeList1.add(authority.getCode());
            });*/
            //判断 传入的 code  在不在集合中
            return codeList.contains(code);

        }
        return false;
    }
}
 @PreAuthorize("@javaPreAuthorize.f1('buss')")
    @GetMapping("/test9")
    public R test9(){
        List<RgDynamic> list = dynamicService.list();
        return R.ok(list);
    }

3:使用menu

@Component
public class JavasmMenuAuthorize {

    public boolean check(String url){
        //查询 用户的信息
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal==null){
            return false;
        }
        LoginUser loginUser = (LoginUser) principal;
        //判断 菜单列表 是否包含当前的地址
        return loginUser.checkMenu(url);
    }
}
 //检查  菜单 是否包含url地址
    public boolean checkMenu(String code){
        //获取 菜单列表
        List<RgAdminMenu> menuList = this.adminUser.getRole().getMenuList();
        //把 菜单的url地址 集中到一起
        //使用流的方式
        Stream<String> childUrlStream = menuList.stream()
                .map(RgAdminMenu::getChildList)
                .flatMap(Collection::stream)
                .map(RgAdminMenu::getUrl);

        Stream<String> firstUrlStream = menuList.stream().map(RgAdminMenu::getUrl);

        List<String> urlList = Stream.concat(childUrlStream, firstUrlStream).collect(Collectors.toList());
        return urlList.contains(code);
    }
 @PreAuthorize("@javasmMenuAuthorize.check('/user/list')")
    @GetMapping("/test10")
    public R test10(){
        List<RgDynamic> list = dynamicService.list();
        return R.ok(list);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值