SpringBoot整合SpringSecurity

 

目录

搭建完SpringBoot项目后导入依赖

配置SpringSecurity

1.基于内存的验证登录

2.自定义验证登录

3.登录成功后处理

4.登录失败后处理

5.处理匿名用户无法访问权限

6.jwt

搭建完SpringBoot项目后导入依赖

<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <version>5.4.2</version>
            <scope>test</scope>
        </dependency>

配置SpringSecurity

1.基于内存的验证登录

package com.dbddd.yyedu.config;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启security注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //认证
    //密码编码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于内存的登录验证
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("mi").password(new BCryptPasswordEncoder().encode("123456")).authorities("administrator")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).authorities("manager")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).authorities("worker");
       
    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有权限对应的人才能进入
        //请求授权规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/stus/list").hasAnyAuthority("manager","administrator")
                .antMatchers("/stus/**").hasAnyAuthority("manager","administrator");
        //没有权限默认跳转到登陆页面,需要开启登陆页面 loginProcessingUrl是指前端请求地址 loginPage是指实际请求地址
        http.formLogin()
        //开启注销功能
        http.logout().logoutSuccessUrl("/");
        //将信息存在cookie中
        http.rememberMe().rememberMeParameter("remember-me");
        http.csrf().disable();//开启模拟请求
//        super.configure(http);
    }

}

现在是没有连接数据库的,内存自定义了用户名,密码,权限。用户访问/stus/list将先跳转到登录页面进行验证,验证通过后,如果该用户具有“manager”或“administrator”的权限就会跳到/stus/list页面。

默认跳到登录页面。输入用户名和密码(这里输入的“mi”,"123456")

当我们输入“guest”,"123456".

跳转到403页面,因为用户guest的权限是work,没有访问list的权限

这里的403页面是自定义的,下面讲一下如果配置

1.编写403页面的路径

2.配置ErroPage

package com.dbddd.yyedu.config;

import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@Configuration
public class ErroPageConfig {
    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                ErrorPage erro403 = new ErrorPage(HttpStatus.FORBIDDEN, "/error-403");
                factory.addErrorPages(erro403);
            }
        };
    }
}

2.自定义验证登录

1.配置configure

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //认证
    //密码编码
    @Resource
    UserService userService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //自定义的登录验证
        auth.userDetailsService(userService)
                .passwordEncoder(new BCryptPasswordEncoder());//密码加密方式
    }

2.Users实现UserDetail接口

package com.dbddd.yyedu.pojo;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;
import java.util.List;

public class Users implements UserDetails {
    private Integer uid;

    private String username;

    private String password;

    private String name;

    private String telephone;

    private Date createdate;

    private String quanxian;

    private List<GrantedAuthority> grant;//权限集合

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

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

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

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

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

    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getTelephone() {
        return telephone;
    }

    public void setTelephone(String telephone) {
        this.telephone = telephone == null ? null : telephone.trim();
    }

    public Date getCreatedate() {
        return createdate;
    }

    public void setCreatedate(Date createdate) {
        this.createdate = createdate;
    }

    public String getQuanxian() {
        return quanxian;
    }

    public void setQuanxian(String quanxian) {
        this.quanxian = quanxian == null ? null : quanxian.trim();
    }

    public List<GrantedAuthority> getGrant() {
        return grant;
    }

    public void setGrant(List<GrantedAuthority> grant) {
        this.grant = grant;
    }
}

 

2.编写userService,实现UserDetailsService

package com.dbddd.yyedu.service;

import com.dbddd.yyedu.pojo.Users;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;


public interface UserService extends UserDetailsService {
    public Users login(String username);
}
package com.dbddd.yyedu.service.Impl;

import com.dbddd.yyedu.dao.UsersMapper;
import com.dbddd.yyedu.pojo.Users;
import com.dbddd.yyedu.pojo.UsersExample;
import com.dbddd.yyedu.service.UserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {
    @Resource
    UsersMapper usersMapper;
    @Override
    public Users login(String username) {
        Users users = null;
        UsersExample usersExample = new UsersExample();
        usersExample.createCriteria().andUsernameEqualTo(username);
        List<Users> usersList = usersMapper.selectByExample(usersExample);
        if(usersList.size()>0){
            users = usersList.get(0);
        }
        return users;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users users = login(username);
        if(users!=null){
            //数据库中的密码加密,security自动校验密码
            users.setPassword(new BCryptPasswordEncoder().encode(users.getPassword()));
            //数据库中查询的用户权限放在集合中
            List<GrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority(users.getQuanxian()));
            users.setGrant(authorities);
            return users;
        }else{
            throw new UsernameNotFoundException("username not found"+username);
        }
    }
}

3.指定自定义登录页面

 protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有权限对应的人才能进入
        //请求授权规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/stus/list").hasAnyAuthority("manager","administrator","normal")
                .antMatchers("/stus/**").hasAnyAuthority("manager","administrator");
        //没有权限默认跳转到登陆页面,需要开启登陆页面 loginProcessingUrl是指前端请求地址 loginPage是指实际请求地址
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/user/login");
        //开启注销功能
        http.logout().logoutSuccessUrl("/");
        //将信息存在cookie中
        http.rememberMe().rememberMeParameter("remember-me");
        http.csrf().disable();//开启模拟请求
    }

4.重启服务器开始登录测试

输入admin,123456

成功跳转到学生列表,登录成功

3.登录成功后处理

后端请求接口登录成功后,如何把数据返回前端呢?

1.首先我们来写一个工具类,用来处理后端返回给前端的数据。

public class Msg {
    //状态码100成功,200失败
    private int code;
    //提示信息
    private String msg;
    //要返回的数据
    private Map<String,Object> extend = new HashMap<String,Object>();
    public static Msg success(){
        Msg msg = new Msg();
        msg.code = 100;
        msg.setMsg("处理成功");
        return msg;
    }
    public static Msg fail(){
        Msg msg = new Msg();
        msg.code = 200;
        msg.setMsg("处理失败");
        return msg;
    }
    public Msg getData(String key, Object value){
        this.getExtend().put(key,value);
        return  this;
    }
    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Map<String, Object> getExtend() {
        return extend;
    }

    public void setExtend(Map<String, Object> extend) {
        this.extend = extend;
    }
}

2.导入fastjson

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
 </dependency>

3.编写配置类

package com.dbddd.yyedu.config;

import com.alibaba.fastjson.JSON;
import com.dbddd.yyedu.pojo.Msg;
import com.dbddd.yyedu.utils.JwtTokenUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class SuccesHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //获取登录成功的用户信息
        UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //给前端一个响应Json
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //将用户信息放到respon中返回给前端
        httpServletResponse.getWriter().write(JSON.toJSONString(Msg.success().getData("user",user)));
    }
}

  4.配置WebSecurityConfig

            .passwordEncoder(new BCryptPasswordEncoder());//密码加密方式
    }
    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有权限对应的人才能进入
        //请求授权规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/list").hasAnyAuthority("manager","administrator","normal")
                .antMatchers("/stus/**").hasAnyAuthority("manager","administrator")
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(customizeEntryPoint);
        //没有权限默认跳转到登陆页面,需要开启登陆页面 loginProcessingUrl是指前端请求地址 loginPage是指实际请求地址
        http.formLogin().loginProcessingUrl("/user/login")
        .successHandler(succesHandler)//登录成功处理
        .failureHandler(failureHandler);//登录失败处理
        //开启注销功能
        http.logout().logoutSuccessUrl("/");
        //将信息存在cookie中
        http.rememberMe().rememberMeParameter("remember-me");
        http.csrf().disable();//开启模拟请求
//        super.configure(http);
    }

}

4.登录失败后处理

import com.alibaba.fastjson.JSON;
import com.dbddd.yyedu.pojo.Msg;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class FailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        //给前端一个响应Json
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //将用户信息放到respon中返回给前端
        httpServletResponse.getWriter().write(JSON.toJSONString(Msg.fail().getData("msg","用户登录失败")));
    }
}

5.处理匿名用户无法访问权限

当用户未登录时,是无权访问其他页面的。

1.先编写一个接口,用来获取用户

import javax.annotation.Resource;

@Controller
@RequestMapping("/user")
public class UserController {
    @Resource
    UserService userService;
    @PreAuthorize("hasAnyAuthority('manager','administrator')")
    @RequestMapping("/getUser/{id}")
    @ResponseBody
    public Msg getUserById(@PathVariable("id") Integer id){
        Users users = userService.getUserById(id);
        return Msg.success().getData("user",users);
    }
}

2.编写配置类

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有权限对应的人才能进入
        //请求授权规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/list").hasAnyAuthority("manager","administrator","normal")
                .antMatchers("/stus/**").hasAnyAuthority("manager","administrator")
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(customizeEntryPoint);
package com.dbddd.yyedu.config;

import com.alibaba.fastjson.JSON;
import com.dbddd.yyedu.pojo.Msg;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CustomizeEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("text/json;charset=utf8");
        httpServletResponse.getWriter().write(JSON.toJSONString(Msg.fail().getData("error","还未登录")));
    }
}

当我们范围/user/getUser/1时,返回的是未登录

6.jwt

这里就不说其原理了,网上有很多博客写的很详细,可以先去了解一下。这里直接上代码。

1.导入标记

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

2.编写工具类

package com.dbddd.yyedu.utils;

import com.dbddd.yyedu.pojo.Users;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
@Component
public class JwtTokenUtils {
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String SECRET = "yyedu";//密钥
    public static final String ISS = "yy";//签发者
    //过期时间一小时:3600秒
    private static final long EXPIPATION = 3600L;
    //选择记住我之后,过期时间7天
   private static final long EXPIPATION_REMEMBER = 604800L;
    //创建token
    public static String createToken(String username,boolean isRemeberMe){
        Long expipation = isRemeberMe?EXPIPATION_REMEMBER:EXPIPATION;
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512,SECRET)//加密规则
                .setIssuer(ISS)//签发者
                .setSubject(username)//Token头部信息
                .setIssuedAt(new Date())//Token创建时间
                .setExpiration(new Date(System.currentTimeMillis()+expipation*1000))//过期时间
                .compact();
    }
    //从token中获取用户名
    public static String getUsernameFromToken(String token){
        return getTokenBody(token).getSubject();
    }
    //验证是否过期
    public static boolean isExpiration(String token){
        return getTokenBody(token).getExpiration().before(new Date());
    }
    //验证Token
    public static Boolean validateToken(String token, UserDetails userDetails){
        Users user = (Users)userDetails;
        final String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername())&&!isExpiration(token));
    }
    //获得Token主体
    private static Claims getTokenBody(String token){
        return Jwts.parser()
                .setSigningKey("yyedu")
                .parseClaimsJws(token)
                .getBody();
    }
}

3.编写过滤器

package com.dbddd.yyedu.filter;

import com.dbddd.yyedu.service.UserService;
import com.dbddd.yyedu.utils.JwtTokenUtils;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
    @Resource
    UserService userService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);//得到头信息
        String username = null;
        String authToken = null;
        if (requestHeader != null && requestHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            authToken = requestHeader.substring(7);
            try {
                username = JwtTokenUtils.getUsernameFromToken(authToken);
            } catch (ExpiredJwtException e) {
            }
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            UserDetails userDetails = userService.loadUserByUsername(username);

            if (JwtTokenUtils.validateToken(authToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }

        }
        chain.doFilter(request, response);
    }
}

4.修改SuccessHandler

import java.io.IOException;
@Component
public class SuccesHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //获取登录成功的用户信息
        UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //获取Token
        String token = JwtTokenUtils.createToken(user.getUsername(),false);
        //System.out.println("token:"+token);
        //将生成的token放到响应头信息中
        httpServletResponse.addHeader("token",token);
        //给前端一个响应Json
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //将用户信息放到respon中返回给前端
        httpServletResponse.getWriter().write(JSON.toJSONString(Msg.success().getData("user",user)));
    }
}

4.配置WebSecurityConfig

 //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有权限对应的人才能进入
        //请求授权规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/list").hasAnyAuthority("manager","administrator","normal")
                .antMatchers("/stus/**").hasAnyAuthority("manager","administrator")
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(customizeEntryPoint);
        //没有权限默认跳转到登陆页面,需要开启登陆页面 loginProcessingUrl是指前端请求地址 loginPage是指实际请求地址
        http.formLogin().loginProcessingUrl("/user/login")
        .successHandler(succesHandler)//登录成功处理
        .failureHandler(failureHandler);//登录失败处理
        //将Token过滤器添加到Security Filter之前
        http.addFilterBefore(jwtAuthorizationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //开启注销功能
        http.logout().logoutSuccessUrl("/");
        //将信息存在cookie中
        http.rememberMe().rememberMeParameter("remember-me");
        http.csrf().disable();//开启模拟请求
//        super.configure(http);
    }

}

5.请求登录页面

已经看到了生成的token

5.携带token去请求用户页面

同样请求学生列表

请求成功!

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值