Spring Boot 使用 Mybatis

依赖

	<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency> 使用: 启动类上注解:@MapperScan("com.dao"),或者每个 dao 接口上添加注解:@Mapper @Mapper 是 MyBatis 的注解,目的是为了让 spring 能够根据 xml 和这个接口动态生成接口的实现。 @Mapper
public interface UserDao {
@Select("select * from user where id=#{id}")
User GetUserbyId(@Param("id") Integer id);
} 注解方式: @Select 是查询类的注解,所有的查询均使用这个
@Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。
@Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值
@Update 负责修改,也可以直接传入对象
@Delete 负责删除 mapper.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.dao.UserDao"> <!-- 可根据自己的需求,是否要使用 -->
<resultMap type="User" id="UserDaoMap">
<id column="id" property="id" jdbcType="INTEGER" />
<result column="userName" property="useName" jdbcType="VARCHAR" />
</resultMap> <select id="GetUserbyId" parameterType="INTEGER" resultMap="UserDaoMap">
select *
from user
where 1=1
and id = #{id,jdbcType=INTEGER}
</select> </mapper> namespace 指定这个 xml 所对应的是哪个 dao 接口。 select 标签的 id 对应的就是 dao 接口中的方法名,parameterType 就是传进来的参数类型。

Mysql 去掉 ONLY_FULL_GROUP_BY

ONLY_FULL_GROUP_BY:
对于GROUP_BY聚合操作,如果在SELECT中的列既没有在GROUP_BY中出现,
本身也不是聚合列(使用SUM,ANG等修饰的列),那么这句SQL是不合法的,因为那一列是不确定的。 SELECT @@global.sql_mode; SET @@global.sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

Spring Security oAuth2

Spring Security 基于 Servlet 过滤器、IoC 和 AOP,
为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。 oAuth2 是授权标准,协议,体现在代码上是接口。实现有 Spring Security 和 Shire。 Access Token 是客户端访问资源服务器的令牌(Access Token 临时的,有一定有效期)。 Refresh Token 的作用是用来刷新 Access Token(Refresh Token 效期非常长)。
刷新接口类似于:http://www.funtl.com/refresh?refresh_token=&client_id=&client_secret=

认证:Authentication 授权:Authorization

认证(Authentication):确定一个用户的身份的过程。

授权(Authorization):判断一个用户是否有访问某个安全对象的权限。

认证与授权中关键的三个过滤器:

	1. UsernamePasswordAuthenticationFilter:
该过滤器用于拦截我们表单提交的请求(默认为/login),进行用户的认证过程。 2. ExceptionTranslationFilter:
该过滤器主要用来捕获处理 Spring Security 抛出的异常,异常主要来源于 FilterSecurityInterceptor。 3. FilterSecurityInterceptor:
该过滤器主要用来进行授权判断。

Spring Security 核心组件

Spring Security 核心组件有:
SecurityContext
SecurityContextHolder
Authentication
Userdetails
AuthenticationManager SecurityContext(安全上下文): 当用户通过认证之后,就会为这个用户生成一个唯一的SecurityContext,里面包含用户的认证信息Authentication。 public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication var1);
} SecurityContextHolder(安全上下文持有者): 在SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。 缺省情况下,SecurityContextHolder使用了ThreadLocal机制来保存每个使用者的安全上下文。 SecurityContextHolder可以工作在以下三种模式之一: MODE_THREADLOCAL (缺省工作模式)
MODE_GLOBAL(所有线程中都相同)
MODE_INHERITABLETHREADLOCAL(存储在线程中,但子线程可以获取到父线程中的 SecurityContext) 修改SecurityContextHolder的工作模式有两种方法: 设置一个系统属性(system.properties):spring.security.strategy 调用SecurityContextHolder静态方法:setStrategyName() 存储当前认证信息:
SecurityContextHolder.getContext().setAuthentication(token); 获取保存在ThreadLocal中的用户信息:
SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Authentication(认证): public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); //获取用户权限信息 Object getCredentials(); // 获取认证信息 Object getDetails(); //获取用户描述信息 Object getPrincipal(); //获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails boolean isAuthenticated(); //是否已经认证 void setAuthenticated(boolean var1) throws IllegalArgumentException;
} UserDetails public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled();
} UserDetailsService UserDetailsService也是一个接口,且只有一个方法loadUserByUsername,用来获取UserDetails。 AuthenticationManager AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication,其定义如下: public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
} AuthenticationManager 的作用就是校验 Authentication,
如果验证失败会抛出 AuthenticationException 异常。

pre-post-annotations

Spring Security 中定义了四个支持使用表达式的注解,分别是:

	@PreAuthorize
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
@PreAuthorize("#user.name.equals('abc')") @PostAuthorize
@PostAuthorize 是在方法调用完成后进行权限检查,
它不能控制方法是否能被调用,只能在方法调用完成后检查权限决定是否要抛出 AccessDeniedException。 @PreFilter
@PreFilter(filterTarget="ids", value="filterObject%2==0"):保留ids集合中的偶数 @PostFilter 其中前两者可以用来在方法调用前或者调用后进行权限检查,
后两者可以用来对集合类型的参数或者返回值进行过滤。

hasRole 和 hasAuthority

角色授权:授权代码需要加 ROLE_ 前缀,Controller 上使用时不要加前缀。

权限授权:设置和使用时,名称保持一至即可。

使用流程

WebSecurityConfig(配置文件)

@Configuration
/**
* @EnableWebSecurity注解有两个作用:
* 1: 加载了WebSecurityConfiguration, 配置安全策略。
* 2: 加载了AuthenticationConfiguration, 配置了认证信息。
*/
@EnableWebSecurity
/**
* prePostEnabled = true:
* 使用表达式时间方法级别的安全性
* 4个注解可用:@PreAuthorize等
*/
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserService userService; @Bean
public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtTokenFilter();
} /**
* 用于认证过程中载入用户信息:public class UserService implements UserDetailsService
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
/**
* SpringSecurity会把匹配规则按注册先后顺序放到一个ArrayList<UrlMapping>中,
* 先注册的规则放前面,后注册的放后面。
*/
httpSecurity
//禁用csrf
.csrf().disable()
/**
* 有多个配置HttpSecurity的配置类时必须设置.antMatcher("/**"),
* 因为不设置的话默认匹配所有,就不会执行到下面的HttpSecurity了。
*
* 需要在类上加注解@Order,指明优先级
*/
.antMatcher("/**")
//设置无状态SessionCreationPolicy(不需要Session)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//登陆页
.formLogin()
.loginPage("/login") //登陆页
.failureUrl("/login") //登陆失败的页面跳转URL
.defaultSuccessUrl("/index") //登录成功后的默认处理页
.permitAll()
.and()
//注销页
.logout()
.logoutUrl("/logout") //注销接口
.logoutSuccessUrl("/login") //注销成功跳转接口
.permitAll()
.and()
//通过 authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护
.authorizeRequests()
//OPTIONS请求全部放行
.antMatchers(HttpMethod.OPTIONS).permitAll()
//其他 post put delete get 请求全部拦截校验
.antMatchers(HttpMethod.POST).authenticated() //需要认证
.antMatchers(HttpMethod.PUT).authenticated()
.antMatchers(HttpMethod.DELETE).authenticated()
.antMatchers(HttpMethod.GET).authenticated(); /**
* addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter)
* 在 beforeFilter 之前添加 filter。
*
* UsernamePasswordAuthenticationFilter:登陆用户密码验证过滤器
*/
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); /**
* 禁用默认响应安全头,添加要用的响应安全头:
* httpSecurity.headers().defaultsDisabled().cacheControl();
*/
httpSecurity.headers().cacheControl();
}
}

JwtTokenFilter过滤器

每次请求都经过过滤器,判断是否持有Token(已经登陆),如果持有Token就进行认证,否则以游客身份认证。

@Component
public class JwtTokenFilter extends OncePerRequestFilter { @Autowired
private UserService userService; @Autowired
private JwtConfig jwtConfig; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
boolean giveFlag = false; //从请求头中获取token
String authHeader = request.getHeader(jwtConfig.getHeader()); //认证
if (authHeader != null && authHeader.startsWith(jwtConfig.getPrefix())) {
//如果token存在,并且格式正确,就从数据库查询UserDetails
UserDetails userDetails = userService.loadUserByToken(authHeader); if (null != userDetails) {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//用户存在,本次会话的权限还未被写入
//生成认证信息
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//将认证信息写入本次会话
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} else {
//用户不存在,以游客身份认证
giveFlag = true;
}
} else {
//token不存在或者格式错误,以游客身份认证
giveFlag = true;
} //给游客授权
if (giveFlag) {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("NORMAL"));
User user = new User("NORMAL", "NORMAL", authorities);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
} chain.doFilter(request, response);
}
}

UserService

@Service
public class UserService implements UserDetailsService { @Autowired
private UserDao userDao; @Autowired
private RoleDao roleDao; @Autowired
private HttpServletRequest request; @Autowired
private BCryptPasswordEncoder encoder; @Autowired
private JwtTokenUtil jwtTokenUtil; @Autowired
private JwtConfig jwtConfig; @Autowired
private RedisTemplate<String, String> redisTemplate; /**
* 登录(校验User,通过校验则生成Token并放入Redis,并返回Token)
*/
public String login(User user) { User dbUser = userDao.findUserByName(user.getName()); //用户名或密码错误
if (null == dbUser || !encoder.matches(user.getPassword(), dbUser.getPassword())) {
throw new UsernameNotFoundException("用户名或密码错误");
}
//用户已被封禁
if (0 == dbUser.getState()) {
throw new RuntimeException("你已被封禁");
} //生成Token
final UserDetails userDetails = this.loadUserByUsername(user.getName());
final String token = jwtTokenUtil.generateToken(userDetails); //key:TOKEN_username,value:Bearer token
redisTemplate.opsForValue().
set(JwtConfig.REDIS_TOKEN_KEY_PREFIX + user.getName(), jwtConfig.getPrefix() + token, jwtConfig.getTime(), TimeUnit.SECONDS); return token;
} @Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
User user = userDao.findUserByName(name); List<SimpleGrantedAuthority> authorities = new ArrayList<>(1);
List<Role> roles = roleDao.findUserRoles(user.getId());
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getName(), null, authorities);
} /**
* 从Token中提取信息,校验Token
*/
public UserDetails loadUserByToken(String authHeader) {
//除去前缀,获取Token
final String authToken = authHeader.substring(jwtConfig.getPrefix().length()); String username = jwtTokenUtil.getUsernameFromToken(authToken); //Token非法
if (null == username) {
return null;
} //通过username从缓存中获取Token:判断Token是否过期,或者Token是否匹配
String redisToken = redisTemplate.opsForValue().get(JwtConfig.REDIS_TOKEN_KEY_PREFIX + username);
if (!authHeader.equals(redisToken)) {
return null;
} List<String> roles = jwtTokenUtil.getRolesFromToken(authToken);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
} return new org.springframework.security.core.userdetails.User(username, null, authorities);
} /**
* 退出登录(清除Redis缓存中的Token)
*/
public void logout() {
String username = jwtTokenUtil.getUsernameFromRequest(request);
redisTemplate.delete(JwtConfig.REDIS_TOKEN_KEY_PREFIX + username);
}
}

JwtTokenUtil

public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
String ISSUER = "iss";
String SUBJECT = "sub";
String AUDIENCE = "aud";
String EXPIRATION = "exp";
String NOT_BEFORE = "nbf";
String ISSUED_AT = "iat";
String ID = "jti";
} @ConfigurationProperties(prefix = "jwt")
@Component
@Data
public class JwtConfig {
public static final String REDIS_TOKEN_KEY_PREFIX = "TOKEN_"; //Redis缓存前缀
private long time = 432000; //5天过期时间
private String secret = "Sara"; //JWT密码
private String prefix = "Bearer "; //Token前缀
private String header = "Authorization"; //存放Token的Header Key
} @Component
public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = -5625635588908941275L; private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
private static final String CLAIM_KEY_ROLES = "roles"; @Autowired
private JwtConfig jwtConfig; /**
* 生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(3);
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); //放入用户名 sub
claims.put(CLAIM_KEY_CREATED, new Date()); //放入token生成时间 created List<String> roles = new ArrayList<>();
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
for (GrantedAuthority authority : authorities) {
roles.add(authority.getAuthority());
}
claims.put(CLAIM_KEY_ROLES, roles); //放入用户权限 roles return generateToken(claims);
} /**
* 生成token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getTime() * 1000))
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
.compact();
} /**
* 校验token
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token); //从token中取出用户名
return (username.equals(userDetails.getUsername())
&&
!isTokenExpired(token) //校验是否过期
);
} /**
* 从request中获取username
*/
public String getUsernameFromRequest(HttpServletRequest request) {
String token = request.getHeader(jwtConfig.getHeader());
token = token.substring(jwtConfig.getPrefix().length()); return token == null ? null : getUsernameFromToken(token);
} /**
* 从token中获取username
*/
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
} /**
* 从token中获取Claims
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
} /**
* 从token中获取创造时间
*/
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long)claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
} /**
* token是否过期
*
* true 过期 false 未过期
*/
public Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
boolean isExpired = expiration.before(new Date());
return isExpired;
} /**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
} /**
* 从token中获取用户角色
*/
public List<String> getRolesFromToken(String authToken) {
List<String> roles;
try {
final Claims claims = getClaimsFromToken(authToken);
roles = (List<String>)claims.get(CLAIM_KEY_ROLES);
} catch (Exception e) {
roles = null;
}
return roles;
}
}

Spring Security 安全认证的更多相关文章

  1. 最简单易懂的Spring Security 身份认证流程讲解

    最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...

  2. Spring Cloud实战 | 第九篇:Spring Cloud整合Spring Security OAuth2认证服务器统一认证自定义异常处理

    本文完整代码下载点击 一. 前言 相信了解过我或者看过我之前的系列文章应该多少知道点我写这些文章包括创建 有来商城youlai-mall 这个项目的目的,想给那些真的想提升自己或者迷茫的人(包括自己- ...

  3. Spring Security 接口认证鉴权入门实践指南

    目录 前言 SpringBoot 示例 SpringBoot pom.xml SpringBoot application.yml SpringBoot IndexController SpringB ...

  4. Spring Security 入门(1-5)Spring Security - 匿名认证

    匿名认证 对于匿名访问的用户,Spring Security 支持为其建立一个匿名的 AnonymousAuthenticationToken 存放在 SecurityContextHolder 中, ...

  5. Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息

    [Please make sure to select the branch corresponding to the version of Thymeleaf you are using] Stat ...

  6. 学习Spring Boot:(二十八)Spring Security 权限认证

    前言 主要实现 Spring Security 的安全认证,结合 RESTful API 的风格,使用无状态的环境. 主要实现是通过请求的 URL ,通过过滤器来做不同的授权策略操作,为该请求提供某个 ...

  7. 学习Spring Security OAuth认证(一)-授权码模式

    一.环境 spring boot+spring security+idea+maven+mybatis 主要是spring security 二.依赖 <dependency> <g ...

  8. Spring Security自定义认证页面(动态网页解决方案+静态网页解决方案)--练气中期圆满

    写在前面 上一回我们简单分析了spring security拦截器链的加载流程,我们还有一些简单的问题没有解决.如何自定义登录页面?如何通过数据库获取用户权限信息? 今天主要解决如何配置自定义认证页面 ...

  9. SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解

    前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Sprin ...

随机推荐

  1. 29 August

    P1352 Bosses' Masquerade 树形DP模板. #include <cstdio> #include <algorithm> using namespace ...

  2. xiugai-去除js注释

    <div class="myLoading"> <div class="svg-wrap"> <svg width="8 ...

  3. nginx用途

     Nginx常用来做静态内容服务器和代理服务器,用来放置静态资源或者转发请求给后面的应用服务. 1. Nginx作为静态服务器使用 作为一个Web服务器,其最主要的任务是作为静态服务器使用. 你需要将 ...

  4. JS-Number 的精度

    JS 使用 IEEE 754 的双精度数表示数字,1 位符号,10 位指数,53 位底数. 所以 JS 数字精度近似为 15.95 位 10 进制(10 ** 15.95). 也就是说整部加小数部分超 ...

  5. python web自动化测试框架搭建(功能&接口)——通用模块

    1.通用模块: config.conf: 公共配置文件,配置报告.日志.截图路径,以及邮件相关配置 [report] reportpath = E:\workspace\WebAutomation\s ...

  6. Vue3.0响应式实现

    基于Proxy // 弱引用映射表 es6 防止对象不能被回收 let toProxy = new WeakMap(); // 原对象: 代理过得对象 let toRaw = new WeakMap( ...

  7. 爬虫之requests模块的使用

    requests模块 概念:基于网络请求的模块 作用:用来模拟浏览器发请求,从而实现爬虫 环境安装:pip install requests 编码流程: 指定url 发起请求 获取响应数据 持久化存储 ...

  8. python面试题之Python如何实现单例模式?

    #使用__metaclass__(元类)的高级python用法 class Singleton2(type): def __init__(cls, name, bases, dict): super( ...

  9. Codeforces 770C Online Courses In BSU (DFS)

    <题目链接> 题目大意:给定$n$个任务,其中有$m$个主线任务,然后$n$个任务中,每个任务都有可能有一个list,表示完成这个任务之前必须要完成的任务,然后现在让你找出,完成这$m$个 ...

  10. NGUI的输入框制作(attach- input filed script的使用)

    一,我们添加一个sprite,给这个sprite添加一个box collider ,然后添加input filed script,如下图: 二,我们给sprite添加一个child的label,然后绑 ...