一、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;
}
}

最新文章

  1. Tarjan三把刀
  2. swift动画小试牛刀
  3. Java程序员的日常 —— Java类加载中的顺序
  4. BI的相关问题[转]
  5. Windows 7上打开IE浏览器报错:无法启动此程序,因为计算机中丢失api-ms-win-core-path-|1-1-0.dll。尝试重新安装该程序以解决此问题。
  6. ajax 开始的loading加载
  7. paip.java c++得到当前类,方法名称以及行号
  8. ipconfig /flushdns 解释
  9. hdu 3342 Legal or Not
  10. linux添加JAVA环境变量
  11. Python调用微博API
  12. MVC基本概念和流程
  13. TF-IDF学习(python实现)
  14. Flash与EEPROM
  15. 【融云分析】如何实现分布式场景下唯一 ID 生成?
  16. rest-framework序列化
  17. Codeforces 387E George and Cards
  18. A Network-based End-to-End Trainable Task-oriented Dialogue System
  19. C++原型模式和模板模式
  20. [Data Access] ORM 原理 (11): 效能議題

热门文章

  1. GPU上如何优化卷积
  2. java后端知识点梳理——MySQL
  3. Netty 框架学习 —— ChannelHandler 与 ChannelPipeline
  4. WizTree——一个扫描快似Everything的硬盘空间分析工具
  5. 『心善渊』Selenium3.0基础 — 1、Selenium自动化测试框架介绍
  6. 【vim】常用总结
  7. .NET 6 亮点之工作负载,它是统一 .NET 的基础
  8. ld-linux-x86-64消耗大量的CPU
  9. excel VBA根据单元格内的逗号把内容拆分行
  10. [源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark