config
CorsConfig.java
package com.woniu.springsecurityday01.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(false)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
SecurityConfig.java
package com.woniu.springsecurityday01.config;
import com.woniu.springsecurityday01.dao.UserDao;
import com.woniu.springsecurityday01.handler.*;
import com.woniu.springsecurityday01.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true,prePostEnabled=true)//作用:开启注解式鉴权
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityService securityService;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private JWTFilter jwtFilter;
/**
* 认证
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
//String encode=passwordEncoder.encode("123");
//自定义用户名和密码
// auth.inMemoryAuthentication().withUser("admin").password(encode).roles("admin");
auth.userDetailsService(securityService);
}
/**
* 自定义登录页面
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin() //告诉框架自定义页面
.loginPage("/login.html") //登录页面地址
.loginProcessingUrl("/dologin")//对应表单提交的action
.successHandler(loginSuccessHandler)
.failureHandler(new LoginFailHandler())
.permitAll();//对上面两个请求放行
//1.无权限2.未登录而登录
http.exceptionHandling()
.accessDeniedHandler(new NOAuthHandler())
.authenticationEntryPoint(new NoLoginHandler());
/**
* 授权
*/
http.authorizeRequests()
//.antMatchers("/hello").hasAuthority("stu:query")
//.antMatchers("/delete").hasAuthority("stu:query")
//.antMatchers("/hello").hasAnyAuthority("stu:query","hello")
//.antMatchers("/hello").hasRole("stu:query")
//.antMatchers("/delete").permitAll() //配置免拦截方法
.anyRequest().authenticated();//所有请求都拦截
/**
* 把jwtfilter注入进来
*/
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
/**
* 把session禁掉
*/
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//跨站脚本攻击关闭
http.csrf().disable();
//允许跨域请求
http.cors();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
controller
HelloController.java
package com.woniu.springsecurityday01.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
/**
* @RequestBody+@Controller=@RestController
*/
public class HelloController {
//@Secured("ROLE_stu:query")
//@PermitAll()
@PreAuthorize("hasAuthority('stu:query')")
@RequestMapping("/hello")
public String hello(){
return "hello security";
}
@RequestMapping("/delete")
public String delete(){
return "hello delete";
}
}
dao
UserDao.java
package com.woniu.springsecurityday01.dao;
import com.woniu.springsecurityday01.domain.Users;
import org.springframework.stereotype.Repository;
@Repository
public interface UserDao {
/**
* 根据账号查用户信息及其权限
*/
Users getUserInfoByAccount(String account);
}
domain
Users.java
package com.woniu.springsecurityday01.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
private Integer id;
private String username;
private String account;
private String password;
private List<String> anth;//该用户拥有的权限
}
handler
JWTFilter.java
package com.woniu.springsecurityday01.handler;
import com.woniu.springsecurityday01.service.SecurityService;
import com.woniu.springsecurityday01.util.JWTUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
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.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 校验jwt
*
* 1:判断请求是否携带jwt
* 否:放行不处理
* 是:走到第二步
* 2:对前端传过来的jwt解密
* 否:放行不处理
* 是:走到第三步
* 3: 获取redis的jwt
* 获取不到:放行不处理
* 获取到:走到第四步
* 4:对比jwt
* 否:放行不处理
* 是:走到第五步
* 5:给jwt续期
*
*/
@Component
public class JWTFilter extends OncePerRequestFilter {
/**
* StringRedisTemplate和RedisTemplate区别
*/
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private SecurityService securityService;
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
/**
* * 1:判断请求是否携带jwt
* * 否:放行不处理
* * 是:走到第二步
*/
String jwt=request.getHeader("jwt");
if(jwt==null){
//交给下一个过滤器处理
filterChain.doFilter(request,response);
return;
}
/**
* 2:对前端传过来的jwt解密
* * 否:放行不处理
* * 是:走到第三步
*/
if(!JWTUtil.decode(jwt)){
filterChain.doFilter(request,response);
return;
}
/**
* 3: 获取redis的jwt
* * 获取不到:放行不处理
* * 获取到:走到第四步
*/
Map payLoad = JWTUtil.getPayLoad(jwt);
String username=(String)payLoad.get("username");
String redisJwt = redisTemplate.opsForValue().get("jwt:" + username);
if(redisJwt==null){
filterChain.doFilter(request,response);
return;
}
/**
* 4:对比jwt
* * 否:放行不处理
* * 是:走到第五步
*/
if(!jwt.equals(redisJwt)){
filterChain.doFilter(request,response);
return;
}
/**
* 5:给jwt续期
*/
redisTemplate.opsForValue().set("jwt:"+ username,jwt,30, TimeUnit.MINUTES);
//把用户信息放到security容器中去
UserDetails userDetails = securityService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken upa=
new UsernamePasswordAuthenticationToken(userDetails.getUsername(),
userDetails.getPassword(),
userDetails.getAuthorities());
//把信息放到security容器中去
SecurityContextHolder.getContext().setAuthentication(upa);
filterChain.doFilter(request,response);
}
}
LoginSuccessHandler.java
package com.woniu.springsecurityday01.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.springsecurityday01.util.JWTUtil;
import com.woniu.springsecurityday01.util.ResponseResult;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
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;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 登录成功处理器
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private StringRedisTemplate redisTemplate;
@SneakyThrows
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication)
throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
User user=(User) authentication.getPrincipal();
String username= user.getUsername();
Map map=new HashMap();
map.put("username",username);
String jwt= JWTUtil.createJWT(map);
//拿jwt干那些事情? 1:放到redis,2:把jwt传到前端
redisTemplate.opsForValue().set("jwt:"+username,jwt,30, TimeUnit.MINUTES);
httpServletResponse.getWriter().write(JSON.toJSONString( new ResponseResult().ok(jwt)));
}
}
其他几个认证同上篇
service
SecurityService.java
package com.woniu.springsecurityday01.service;
import com.woniu.springsecurityday01.dao.UserDao;
import com.woniu.springsecurityday01.domain.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SecurityService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDao userDao;
/**
* username:页面传过来的用户名
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users userInfo=userDao.getUserInfoByAccount(username);
if(userInfo!=null){
String join =String.join(",",userInfo.getAnth());//集合以
//根据username 去数据库查该用户信息
return new User(userInfo.getAccount(),passwordEncoder.encode(userInfo.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList(join));
}else {
throw new UsernameNotFoundException("用户不存在");
}
}
}
util
JWTUtil.java
package com.woniu.springsecurityday01.util;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
/**
* jwt 工具类
* 1.创建jwt
* 2.解密jwt
*/
public class JWTUtil {
private static final String KEY="kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk";
public static String createJWT(Map map) throws JOSEException{
//第一部分:头部,主要放jwt自我描述部分,比如加密方式
JWSHeader header=new JWSHeader.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT).build();
//第二部分:载荷部分,主要放用户登录成功后,一些个人信息(注意:不要放敏感信息)
Payload payload=new Payload(map);
//第三部分:签名部分,(头部+载荷)通过一个密钥加密后得到的
JWSObject jwsObject=new JWSObject(header,payload);
JWSSigner jwsSigner=new MACSigner(KEY);
//拿到密钥加密
jwsObject.sign(jwsSigner);
return jwsObject.serialize();
}
public static boolean decode(String jwt) throws ParseException, JOSEException {
//parse()把字符串转换一个对象
JWSObject jwsObject= JWSObject.parse(jwt);
JWSVerifier jwsVerifier=new MACVerifier(KEY);
//解密方法verify()
return jwsObject.verify(jwsVerifier);
}
/**
* 根据jwt获取其中的载荷部分
* @param jwt
* @return
*/
public static Map getPayLoad(String jwt) throws ParseException {
//parse()把字符串转换一个对象
JWSObject jwsObject= JWSObject.parse(jwt);
Payload payload = jwsObject.getPayload();
Map<String, Object> map = payload.toJSONObject();
return map;
}
}
ResponseEnum.java
package com.woniu.springsecurityday01.util;
public enum ResponseEnum {
LOGIN_SUCCESS(200,"okkk"),
LOGIN_FAIL(500,"faill"),
NO_LOGIN(20001,"NOLOGIN"),
NO_AUTH(5000,"NOAUTH")
;
private Integer code;
private String msg;
ResponseEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
ResponseEnum() {
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
ResponseResult.java
package com.woniu.springsecurityday01.util;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Data
public class ResponseResult<T> {
private Integer code;
private String msg;
private T data;
public ResponseResult<T> ok(T t){
return new ResponseResult(ResponseEnum.LOGIN_SUCCESS.getCode(),t);
}
public static final ResponseResult<Void> LOGIN_SUCCESS=
new ResponseResult(ResponseEnum.LOGIN_SUCCESS.getCode(),
ResponseEnum.LOGIN_SUCCESS.getMsg());
public static final ResponseResult<Void> LOGIN_FAIL=
new ResponseResult(ResponseEnum.LOGIN_FAIL.getCode(),
ResponseEnum.LOGIN_FAIL.getMsg());
public static final ResponseResult<Void> NO_LOGIN=
new ResponseResult(ResponseEnum.NO_LOGIN.getCode(),
ResponseEnum.NO_LOGIN.getMsg());
public static final ResponseResult<Void> NO_AUTH=
new ResponseResult(ResponseEnum.NO_AUTH.getCode(),
ResponseEnum.NO_AUTH.getMsg());
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
TestJWT.java
package com.woniu.springsecurityday01.util;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
public class TestJWT {
private static final String KEY="kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk";
public static void main(String[] args) throws Exception{
Map map1=new HashMap();
map1.put("username","admin");
String jwt=JWTUtil.createJWT(map1);
Map payload1=JWTUtil.getPayLoad(jwt);
System.out.println(payload1.get("username"));
//第一部分:头部,主要放jwt自我描述部分,比如加密方式
JWSHeader header=new JWSHeader.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT).build();
System.out.println(header.toBase64URL());
//第二部分:载荷部分,主要放用户登录成功后,一些个人信息(注意:不要放敏感信息)
Map map=new HashMap();
map.put("username","admin");
map.put("gender","男");
Payload payload=new Payload(map);
System.out.println(payload.toBase64URL());
//第三部分:签名部分,(头部+载荷)通过一个密钥加密后得到的
JWSObject jwsObject=new JWSObject(header,payload);
JWSSigner jwsSigner=new MACSigner(KEY);
//拿到密钥加密
jwsObject.sign(jwsSigner);
System.out.println(jwsObject.serialize());
//看看能不能解密
String sigin="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnZW5kZXIiOiLnlLciLCJ1c2VybmFtZSI6ImFkbWluIn0.a3N4ec6XYQjDwrf5SykTVBPZF1NmIWdIwZT4hxia8sU";
System.out.println(decode(sigin));
}
/**
* 拿到jwt根据系统的密码,看能不能解开
* @return
*/
public static boolean decode(String jwt) throws ParseException, JOSEException {
//parse()把字符串转换一个对象
JWSObject jwsObject= JWSObject.parse(jwt);
JWSVerifier jwsVerifier=new MACVerifier(KEY);
//解密方法verify()
return jwsObject.verify(jwsVerifier);
}
}
启动类
Springsecurityday01Application.java
package com.woniu.springsecurityday01;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.woniu.springsecurityday01.dao")
public class Springsecurityday01Application {
public static void main(String[] args) {
SpringApplication.run(Springsecurityday01Application.class, args);
}
}
UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.woniu.springsecurityday01.dao.UserDao">
<resultMap id="userMap" type="com.woniu.springsecurityday01.domain.Users">
<result property="id" column="id"></result>
<result property="username" column="username"></result>
<result property="account" column="account"></result>
<result property="password" column="password"></result>
<collection property="anth" ofType="java.lang.String">
<result column="anth_code"></result>
</collection>
</resultMap>
<select id="getUserInfoByAccount" resultMap="userMap">
SELECT
us.id,
us.username,
us.account,
us.password,
ta.anth_code
FROM
users us
left join t_user_anth tua on us.id=tua.user_id
left join t_anth ta on tua.anth_id=ta.id
WHERE account=#{account}
</select>
</mapper>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.woniu</groupId>
<artifactId>springsecurityday01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurityday01</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--springboot整合security坐标-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- mysql-connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.12</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.11.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions><!-- 从依赖关系中排除 -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>