springboot-6-springSecurity
一、Spring Security的基本配置
安全需要在设计网站之初就需要做好设计
可以做到:
- 功能权限
- 访问权限
- 菜单权限
这些权限虽然用拦截器过滤器也能实现,但是很麻烦,所以我们一般使用框架实现
几个重要类:
- WebSecurityConfigureAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebEnableSecurity模式
Spring Security的两个主要目标是“认证”和“授权”(即访问控制)
“认证”Authentication
“授权”Authorization
这个概念是通用的,而不只是在Spring Security中存在
流程:
导入依赖,是spring系列的框架
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
写控制器
@RestController
public class Hello {
@GetMapping("/hello")
public String getHello(){
return "hello security";
}
}
访问:http://localhost:8080/hello
用户名是user
密码为:
登录进去之后才进入了hello的放回界面
配置文件指定用户名和密码:
application.properties
spring.security.user.name=admin
spring.security.user.password=123456
spring.security.user.roles=admin
配置类实现更多的配置:
这是一种基于内存的配置,通过继承WebSecurityConfigurerAdapter
要与数据库认证区分开来,配置角色时不需要再添加“ROLE_”前缀
1、指定用户名和密码和职责(认证)
1、添加配置类
2、继承WebSecurityConfigurerAdapter
3、重写configure(AuthenticationManagerBuilder auth)方法
4、添加加密方式
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//springboot要求必须使用一种加密方式
//此处使用NoOpPasswordEncoder,即无密码加密方式
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置两个用户
auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","USER")
.and()
.withUser("wang").password("123").roles("USER");
}
}
2、配置受保护资源(授权)
重写的是configure(HttpSecurity http)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加了三个成员,分别是数据库管理员、管理员、和用户
auth.inMemoryAuthentication()
.withUser("root").password("123456").roles("ADMIN","DBA")
.and()
.withUser("admin").password("123456").roles("ADMIN")
.and()
.withUser("wang").password("123456").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//管理员--->管理员角色
.antMatchers("/admin/**").hasRole("ADMIN")
//用户--->用户角色或者管理员角色有其一即可
.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')")
//数据库--->管理员角色和数据库管理员角色都要有才能访问
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
//除去上面的请求模式需要特定的角色之外
// 其他请求都需要先经过认证,才能访问
.anyRequest().authenticated()
.and()
/*表单登录
* 登录url为"/login",注意这个“/”
* permitall表示所有与登录相关的接口都不需要认证就可以访问
* */
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
//表示关闭csrf
.csrf().disable();
}
添加控制器--测试
@RestController
public class Hello {
//管理员
@GetMapping("/admin/hello")
public String admin(){
return "hello admin";
}
//用户
@GetMapping("/user/hello")
public String user(){
return "hello user";
}
//数据库管理员
@GetMapping("/db/hello")
public String db(){
return "hello dba";
}
//普通的hello
@GetMapping("/hello")
public String Hello(){
return "hello";
}
}
会发现只要登录了就可以访问/hello,还有这个用户对应的hello页面,其他以此类推
登录表单详细配置:
就是跟改上面授权中的.formLogin().loginProcessingUrl("/login").permitAll()
这一段配置
//表单提交数据
.formLogin()
//下面这个是登录界面,此处没有写这个界面,所以用postman测试
.loginPage("/login_page")
//下面三行值得是提交信息给哪个url,而且用的是哪个参数名称
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("password")
//在这个成功操作的方法里面放一个实体静态类
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//从认证成功那里得到关键信息
Object principal = authentication.getPrincipal();
PrintWriter out = response.getWriter();
response.setStatus(200);
//这个map就是我们要放回的json信息
HashMap<String, Object> map = new HashMap<>();
map.put("status",200);
map.put("msg",principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(401);
HashMap<String, Object> map = new HashMap<>();
map.put("status",401);
if (exception instanceof LockedException){
map.put("msg","账户被锁,登陆失败");
}else if(exception instanceof BadCredentialsException){
map.put("msg","账户名或密码输入错误,登录失败");
}else if(exception instanceof DisabledException){
map.put("msg","账户被禁用,登陆失败");
}else if(exception instanceof CredentialsExpiredException){
map.put("msg","密码已过期,登陆失败");
}else if(exception instanceof AccountExpiredException){
map.put("msg","账户已过期,登陆失败");
}else{
map.put("msg","登陆失败");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()
此处主要是返回一段json数据,数据提交给/login接口
所以可以使用postman测试
注销登录操作:
注销登录,就是类似于qq下线一样
在后面加上一个新的板块
.and()
//开启注销登录的配置
.logout()
//配置注销登录提交星系交给哪个接口
.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
//这个地方根据需要完成一些数据清除工作,如cookie
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/login_page");
}
})
多个Http Security同时配置:
略(见书)
二、密码加密
三、基于数据库认证
流程:
先创建好表
user、role、user_role三个表
![image-20210707125857051](https://img2020.cnblogs.com/blog/2309666/202107/2309666-20210715104156259-1119834196.png)
![image-20210707125857051](https://img2020.cnblogs.com/blog/2309666/202107/2309666-20210715104156259-1119834196.png)
填充数据
![image-20210707131719146](https://img2020.cnblogs.com/blog/2309666/202107/2309666-20210715104156482-81242353.png)
需要再整合mybatis
导入依赖
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
配置数据库
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
创建对应pojo类,Role类和User类
@Data
public class Role {
private Integer id;
private String name;
private String nameZh;
}
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private boolean enabled;
private boolean locked;
private List<Role> roles;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
/*======================下面为重写方法======================*/
//这个方法用于获取当前用户所具有的角色对象
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role:roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
//因为数据库中没有创建过期未过期的字段,故直接返回true
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
创建UserMapper接口和UserMapper.xml
@Mapper
@Repository
public interface UserMapper {
//通过用户名去数据库找这个用户,如果没找到就返回一个账户不存在的错误
//如果找到了用户,就继续查找该用户对应的角色信息,并将获取到的用户信息返回,系统会自动去比对密码
User loadUseByUsername(String username);
//通过用户id来找到这个其对应的角色
List<Role> getUserRolesByUid(Integer id);
}
<?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.wang.dao.UserMapper">
<!--User loadUseByUsername(String username);-->
<select id="loadUseByUsername" parameterType="string" resultType="user">
select * from user where username = # {username}
</select>
<!--List<Role> getUserRolesByUid(Integer id);-->
<select id="getUserRolesByUid" parameterType="integer" resultType="role">
select * from role r , user_role ur where r.id = ur.rid and ur.uid = #{id}
</select>
</mapper>
创建UserService
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
//参数即为表单输入的参数
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUseByUsername(username);
if (user == null){
throw new UsernameNotFoundException("账户不存在");
}
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
配置Spring Security
User类怎么写
User类必须要实现UserDetails,里面有七个方法需要重写,
方法 | 解释 |
---|---|
getAuthorities() | 获取当前用户对象所具有的角色信息 |
getUsername() | 获取当前用户名 |
isCredentialsNonExpired() | 当前用户是否过期(字段) |
isAccountNonLocked() | 当前用户是否锁定(字段) |
isEnabled() | 当前账户是否可用(字段) |
getPassword() | 获取当前用户密码 |
isCredentialsNonExpired() | 当前密码是否过期(密码字段) |
getAuthorities()如何获取到角色信息的:
在User类中用户所具有的角色储存在了roles属性中,所以先创建一个SimpleGrantedAuthority的集合,
再遍历每个role,用他们的name创建一个个SimpleGrantedAuthority实体类,放到集合中去,最后放回这个集合即可。
UserService类怎么写的
必须要实现UserDetailsService类,并重写里面的loadUserByUsername
方法,这个方法可以直接获取到输入的username,把这个username通过usermapper去查找
如果没找到,我们需要自定自己的输出错误
如果找到了,系统会自动会匹配他的密码,根据相关数据,返回不同的错误
在然后我们通过这个user的id通过usermapper的另一个方法找到他的角色,再把这个角色又赋值给这个user,再返回这个user实例,实现了通过username加载user
一个完整的基于内存的配置
package com.wang.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.security.auth.login.CredentialException;
import javax.security.auth.login.CredentialExpiredException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
//@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//把这个实例放到IOC容器中去是为了使密码无加密
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加了三个成员,分别是数据库管理员、管理员、和用户
auth.inMemoryAuthentication()
.withUser("root").password("123456").roles("ADMIN","DBA")
.and()
.withUser("admin").password("123456").roles("ADMIN")
.and()
.withUser("wang").password("123456").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//管理员--->管理员角色
.antMatchers("/admin/**").hasRole("ADMIN")
//用户--->用户角色或者管理员角色有其一即可
.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')")
//数据库--->管理员角色和数据库管理员角色都要有才能访问
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
//除去上面的请求模式需要特定的角色之外
// 其他请求都需要先经过认证,才能访问
.anyRequest().authenticated()
.and()
//表单提交数据
.formLogin()
//下面这个是登录界面,此处没有写这个界面,所以用postman测试
.loginPage("/login_page")
//下面三行值得是提交信息给哪个url,而且用的是哪个参数名称
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("password")
//在这个成功操作的方法里面放一个实体静态类
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//从认证成功那里得到关键信息
Object principal = authentication.getPrincipal();
PrintWriter out = response.getWriter();
response.setStatus(200);
//这个map就是我们要放回的json信息
HashMap<String, Object> map = new HashMap<>();
map.put("status",200);
map.put("msg",principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(401);
HashMap<String, Object> map = new HashMap<>();
map.put("status",401);
if (exception instanceof LockedException){
map.put("msg","账户被锁,登陆失败");
}else if(exception instanceof BadCredentialsException){
map.put("msg","账户名或密码输入错误,登录失败");
}else if(exception instanceof DisabledException){
map.put("msg","账户被禁用,登陆失败");
}else if(exception instanceof CredentialsExpiredException){
map.put("msg","密码已过期,登陆失败");
}else if(exception instanceof AccountExpiredException){
map.put("msg","账户已过期,登陆失败");
}else{
map.put("msg","登陆失败");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()
.and()
//开启注销登录的配置
.logout()
//配置注销登录提交星系交给哪个接口
.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
//这个地方根据需要完成一些数据清除工作,如cookie
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/login_page");
}
})
.and()
.csrf()
.disable();
}
}
一个完整的基于数据库的配置
@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//配置角色的方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/db/**").hasRole("dba")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
.csrf().disable();
}
}
角色继承
1、基于security框架的角色继承
在配置类中添加一个bean到IOC容器中去
这个配置保证了dba的权限包含了admin的,admin又包含了dba的
@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_dba > ROLE_admin > ROLE_user";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
2、动态配置权限
使用httpSecurity配置的认证授权规则还是不够灵活,无法实现资源和角色的动态调整,需要开发者自定义权限配置
(1)、数据库设计
在原来的数据库的基础上再增加资源表和资源角色表
(2)添加配置组件类
略
四、整合shiro
流程:
添加依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
<!--整合thymeleaf技术-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<!--不需要版本号-->
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
配置基本信息
# shiro配置
# 开启shiro配置
shiro.enabled=true
# 开启shiroweb配置
shiro.web.enabled=true
# 表示登录地址,默认为/login.jsp
shiro.loginUrl=/login
# 登陆成功地址,默认为/
shiro.successUrl=/index
# 表示当登录之后到未授权界面默认跳转地址
shiro.unauthorizedUrl=/unauthorized
# 表示是否允许通过url参数实现会话跟踪,默认为true(如果支持cookie的话一般设为false)
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
# 表示是否允许通过cookie实现会话跟踪,默认为true
shiro.sessionManager.sessionIdCookieEnabled=true
配置shiro
package com.wang.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
//注意这个地方的realm对应的包时shiro下的包
@Bean
public Realm realm(){
TextConfigurationRealm realm = new TextConfigurationRealm();
realm.setUserDefinitions("sang=123,user\n admin=123,admin");
realm.setRoleDefinitions("admin=read,write\n user=read");
return realm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterFactoryBean(){
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//anon表示匿名,logout表示注销操作,authc表示需要登录操作
chainDefinition.addPathDefinition("/login","anon");
chainDefinition.addPathDefinition("/dologin","anon");
chainDefinition.addPathDefinition("/logout","logout");
chainDefinition.addPathDefinition("/**","authc");
return chainDefinition;
}
//如果在thymeleaf中不使用shiro标签的话,不需要这个bean
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
对应的control
package com.wang.control;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class LoginControl {
//dologin为登录接口
@PostMapping("doLogin")
public String doLogin(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
/* 尝试登录
* 登录失败:带一个错误信息去登录界面
* 登录成功:重定向到/index
* */
try{
subject.login(token);
}catch (AuthenticationException e){
model.addAttribute("error","用户名或密码错误");
return "login";
}
//这个重定向其实无所谓,因为在配置文件中已经配置了
return "redirect:/index";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String getAdmin(){
return "admin";
}
@RequiresRoles(value = {"admin","user"},logical = Logical.OR)
@GetMapping("/user")
public String getUser(){
return "user";
}
}
配置对应的mvc配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//这些是不需要角色就可以访问到的接口
registry.addViewController("/login").setViewName("login");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/unauthorized").setViewName("unauthorized");
}
}
还有五个界面, login.html , index.html , admin.html , user.html , unauthorized.html
<!DOCTYPE html>
<html lang="en" xmlns:th=http://www.thymeleaf.org
xmlns:sec=http://www.thymeleaf.org/extras/spring-security
xmlns:shiro=http://www.pollix.at/thymeleaf/shiro>
<head>
<title>Title</title>
</head>
<body>
<form th:action="@{/doLogin}" method="post">
username: <input type="text" name="username"><br/>
password: <input type="password" name="password"><br/>
<div th:text="${error}"></div>
<input type="submit" value="提交">
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th=http://www.thymeleaf.org
xmlns:sec=http://www.thymeleaf.org/extras/spring-security
xmlns:shiro=http://www.pollix.at/thymeleaf/shiro>
<head>
<title>Title</title>
</head>
<body>
<!--<shiro:principal/>可以查看用户名-->
<h3>hello,<shiro:principal/></h3>
<h4><a th:href="@{/logout}">注销操作</a></h4>
<h4><a shiro:hasRole="admin" th:href="@{/admin}">管理员界面</a></h4>
<h4><a shiro:hasAnyRoles="admin,user" th:href="@{/user}">普通用户界面</a></h4>
</body>
</html>
五、shiro的基于数据库的认证
流程:
需要一个通过id找到密码的userService
@Service
public class UserLoginService {
@Autowired
UserLoginMapper userLoginMapper;
public UserLogin getUserLogin(String account){
return userLoginMapper.getUserLogin(account);
}
}
然后再ShiroConfig中自定义自己的Realm即可
@Configuration
public class ShiroConfig {
//注意这个地方的realm对应的包时shiro下的包
@Bean
public Realm realm(){
// TextConfigurationRealm realm = new TextConfigurationRealm();
// realm.setUserDefinitions("sang=123,user\n admin=123,admin");
// realm.setRoleDefinitions("admin=read,write\n user=read");
// return realm;
return new AuthRealm();
}
@Bean
public ShiroFilterChainDefinition ShiroFilterFactoryBean(){
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//anon表示匿名,logout表示注销操作,authc表示需要登录操作
chainDefinition.addPathDefinition("/login","anon");
chainDefinition.addPathDefinition("/dologin","anon");
chainDefinition.addPathDefinition("/logout","logout");
chainDefinition.addPathDefinition("/**","authc");
return chainDefinition;
}
//如果在thymeleaf中不使用shiro标签的话,不需要这个bean
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
class AuthRealm extends AuthorizingRealm{
@Autowired
UserLoginService userLoginService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//认证
//从token中获取登录时的账户和密码
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
String account = userToken.getUsername();
String passwordToken = new String(userToken.getPassword());
//获取数据库中的密码
String password = userLoginService.getUserLogin(account).getPassword();
//如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息
if (passwordToken == null || !passwordToken.equals(password)){
return null;
}
//认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
return new SimpleAuthenticationInfo(account,password,getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//授权
//这个地方也可以配置数据库,进行id和角色的匹配
return null;
}
}
springboot-6-springSecurity的更多相关文章
- boke练习: springboot整合springSecurity出现的问题,传递csrf
boke练习: springboot整合springSecurity出现的问题,传递csrf freemarker模板 在html页面中加入: <input name="_csrf&q ...
- boke练习: springboot整合springSecurity出现的问题,post,delete,put无法使用
springboot 与 SpringSecurity整合后,为了防御csrf攻击,只有GET|OPTIONS|HEAD|TRACE|CONNECTION可以通过. 其他方法请求时,需要有token ...
- SpringBoot使用SpringSecurity搭建基于非对称加密的JWT及前后端分离的搭建
SpringBoot使用SpringSecurity搭建基于非对称加密的JWT及前后端分离的搭建 - lhc0512的博客 - CSDN博客 https://blog.csdn.net/lhc0512 ...
- SpringBoot整合SpringSecurity简单实现登入登出从零搭建
技术栈 : SpringBoot + SpringSecurity + jpa + freemark ,完整项目地址 : https://github.com/EalenXie/spring-secu ...
- 【使用篇二】SpringBoot集成SpringSecurity(22)
SpringSecurity是专门针对基于Spring项目的安全框架,充分利用了依赖注入和AOP来实现安全管控.在很多大型企业级系统中权限是最核心的部分,一个系统的好与坏全都在于权限管控是否灵活,是否 ...
- SpringBoot+JWT+SpringSecurity+MybatisPlus实现Restful鉴权脚手架
若图片查看异常,请前往掘金查看:https://juejin.im/post/5d1dee34e51d4577790c1cf4 前言 JWT(json web token)的无状态鉴权方式,越来越流行 ...
- SpringBoot 集成SpringSecurity JWT
目录 1. 简介 1.1 SpringSecurity 1.2 OAuth2 1.3 JWT 2. SpringBoot 集成 SpringSecurity 2.1 导入Spring Security ...
- SpringBoot整合SpringSecurity示例实现前后分离权限注解
SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证 作者:Sans_ juejin.im/post/5da82f066fb9a04e2a73daec 一.说 ...
- 9、SpringBoot整合之SpringBoot整合SpringSecurity
SpringBoot整合SpringSecurity 一.创建项目,选择依赖 选择Spring Web.Thymeleaf即可 二.在pom文件中导入相关依赖 <!-- 导入SpringSecu ...
- SpringBoot整合SpringSecurity实现JWT认证
目录 前言 目录 1.创建SpringBoot工程 2.导入SpringSecurity与JWT的相关依赖 3.定义SpringSecurity需要的基础处理类 4. 构建JWT token工具类 5 ...
随机推荐
- SpringBoot注解 + 详解
可以使用Ctrl + F搜索,也可以右侧目录自行检索 @SpringBootApplication 包含了@ComponentScan.@Configuration和@EnableAutoConfig ...
- mybatis学习——实现分页
首先回顾一下分页的sql语句: SELEC * FROM 表名 LIMIT startIndex,pageSize tips: *startIndex:起始的位置(从哪个元素开始分页) *pageSi ...
- 透彻理解USB总线应用之枚举
Hello,大家好,今天我们来讨论一下USB总线中的枚举(Enumeration),首先简单介绍一下USB系统的基本架构,它由USB主机.USB设备与USB电缆(本文忽略它)组成,如下图所示: 最常见 ...
- gRPC入门—golang实现
1.RPC 1.1 什么是RPC RPC(Remote Procedure Call),即远程过程调用,过程就是方法,简单来说,它就是一种能够像调用本地方法一样调用远程计算机进程中的方法的技术,在这种 ...
- C++中封装和继承的访问权限
众所周知,C++面向对象的三大特性为:封装,继承和多态.下面我们就先对封装做一些简单的了解.封装是通过C++中的类来完成的,类是一种将抽象转换为用户定义类型的工具.类的定义如下: class circ ...
- Kubernetes网络的iptables模式和ipvs模式支持ping分析
1.iptables模式无法ping通原因分析 iptables模式下,无法ping通任何svc,包括clusterip.所有ns下,下面来分析原因: 查看kubernetes的网络模式 curl 1 ...
- jenkins 修改端口后无法启动的解决思路
一.问题 jenkins网站绑定域名后直接通过域名访问使用的是8080端口,为方便用户访问因此须监听80端口,而为了安全起见linux一般不用root身份运行,综上,需要以普通用户来运行监听80端口时 ...
- 第8章:深入理解Service
8.1 Service存在的意义 防止Pod失联(服务发现) 定义一组Pod的访问策略(负载均衡) 8.2 为什么要使用Service Kubernetes Pod`是平凡的,由`Deployment ...
- 使用VS2017开发APP中使用VUE.js开发遇到打包出来的android文件 在低版本的android(4.3)中无法正常使用
使用VS2017开发VUE的APP应用遇到的问题集合 1, 打包出来的apk文件在Android 6.0版本以上手机可以正常打开,在Android 4.3版本手机上无法打开 原因:一开始猜测是不是V ...
- SpringBoot统一处理返回结果和异常情况
如果文章有帮助到你,还请点个赞或留下评论 原因 在springboot项目里我们希望接口返回的数据包含至少三个属性: code:请求接口的返回码,成功或者异常等返回编码,例如定义请求成功. messa ...