目录
搭建完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去请求用户页面
同样请求学生列表
请求成功!