Шаг 2. основные понятия
Мы будем заниматься именно безопасностью которую предоставляет нам сервер приложений. Для начала, стоит познакомиться с некоторыми понятиями.
Аутентификация – проверка на существование человека(зарегистрированного) в нашем приложении. Является ли он тем, за кого себя выдает?
Авторизация – проверка прав аутентифицированного пользователя выполнять конкретные действия.
How JAAS Works
When using JAAS in an application, several APIs are involved:
We’ll use the default implementation for the Configuration API and provide our own implementations for the CallbackHandler and the LoginModule APIs.
3. commit()
If all calls to LoginModule#login succeed, we update the Subject with an additional Principal:
LoginModule Configuration
JAAS uses the Configuration service provider to load LoginModules at runtime. By default, it provides and uses the ConfigFile implementation where LoginModules are configured through a login file. For example, here is the content of the file used for our LoginModule:
jaasApplication {
com.baeldung.jaas.loginmodule.InMemoryLoginModule required debug=true;
};
As we can see, we’ve provided the fully qualified class name of the LoginModule implementation, a required flag, and an option for debugging.
Finally, note that we can also specify the login file through the java.security.auth.login.config system property:
$ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config
We can also specify one or more login files through the property login.config.url in the Java security file, ${java.home}/jre/lib/security/java.security:
Authentication
Firstly, an application initializes the authentication process by creating a LoginContext instance. To do so, we can take a look at the full constructor to have an idea about what we need as parameters:
LoginContext(String name, Subject subject, CallbackHandler callbackHandler, Configuration config)
Here, we’ll be using the overloaded constructor where we’ll be providing our CallbackHandler implementation:
LoginContext(String name, CallbackHandler callbackHandler)
Now that we have a CallbackHandler and a configured LoginModule, we can start the authentication process by initializing a LoginContext object:
LoginContext loginContext = new LoginContext("jaasApplication", new ConsoleCallbackHandler());
1. Defining Permissions
An access control right or permission is the ability to execute an action on a resource. We can implement a permission by subclassing the Permission abstract class. To do so, we need to provide a resource name and a set of possible actions.
For example, we can use FilePermission to configure access control rights on files. Possible actions are read, write, execute, and so on. For scenarios where actions are not necessary, we may simply use the BasicPermision.
2. Granting Permissions
Usually, we don’t need to know the policy file syntax because we can always use the Policy Tool to create one. Let’s take a look at our policy file:
Security developer’s guide
To authorize access to resources, applications first need to authenticate the source of the request. The JAAS framework defines the term subject to represent the source of a request. A subject may be any entity, such as a person or a service. Once the subject is authenticated, a javax.security.auth.Subject
is populated with associated identities, or Principals. A Subject
may have many Principal
s. For example, a person may have a name Principal
(“John Doe”) and a SSN Principal
(“123-45-6789”), which distinguish it from other subjects.
A Subject
may also own security-related attributes, which are
referred to as credentials; see the section Credentials. Sensitive credentials that require special protection,
such as private cryptographic keys, are stored within a private credential
Set
. Credentials intended to be shared, such as public key
certificates, are stored within a public credential Set
. Different
permissions are required to access and modify the different credential
Set
s.
Subjects are created using these constructors:
public Subject();
public Subject(boolean readOnly, Set principals,
Set pubCredentials, Set privCredentials);
The first constructor creates a Subject
with empty (non-null) Set
s of Principal
s and credentials. The second constructor creates a Subject
with the specified Set
s of Principal
s and credentials. It also has a boolean argument which can be used to make the Subject
read-only. In a read-only Subject
, the Principal
and credential Set
s are immutable.
An application writer does not have to instantiate a Subject
. If the application instantiates a LoginContext
and does not pass a Subject
to the LoginContext
constructor, the LoginContext
instantiates a new empty Subject
. See the LoginContext section.
If a Subject
was not instantiated to be in a read-only state, it can be set read-only by calling the following method:
public void setReadOnly();
A javax.security.auth.AuthPermission
with target “setReadOnly” is required to invoke this method. Once in a read-only state, any attempt to add or remove Principal
s or credentials will result in an IllegalStateException
being thrown. The following method may be called to test a Subject
‘s read-only state:
public boolean isReadOnly();
To retrieve the Principal
s associated with a Subject, two methods are available:
public Set getPrincipals();
public Set getPrincipals(Class c);
The first method returns all Principal
s contained in the Subject
, while the second method only returns those Principal
s that are an instance of the specified Class c
, or an instance of a subclass of Class c
. An empty set will be returned if the Subject
does not have any associated Principal
s.
To retrieve the public credentials associated with a Subject
, these methods are available:
public Set getPublicCredentials();
public Set getPublicCredentials(Class c);
The behavior of these methods is similar to that for the getPrincipals
methods, except in this case the public credentials are being obtained.
To access private credentials associated with a Subject
, the following methods are available:
public Set getPrivateCredentials();
public Set getPrivateCredentials(Class c);
The behavior of these methods is similar to that for the getPrincipals
and getPublicCredentials
methods.
To modify or operate upon a Subject
‘s Principal
Set
, public credential Set
, or private credential Set
, callers use the methods defined in the java.util.Set
class. The following example demonstrates this:
Subject subject;
Principal principal;
Object credential;
. . .
// add a Principal and credential to the Subject
subject.getPrincipals().add(principal);
subject.getPublicCredentials().add(credential);
Note: An AuthPermission
with target “modifyPrincipals”, “modifyPublicCredentials”, or “modifyPrivateCredentials” is required to modify the respective Set
s. Also note that only the sets returned via the getPrincipals()
, getPublicCredentials()
, and getPrivateCredentials()
methods with no arguments are backed by the Subject
‘s respective internal sets. Therefore any modification to the returned set affects the internal sets as well. The sets returned via the getPrincipals(Class c)
, getPublicCredentials(Class c)
, and getPrivateCredentials(Class c)
methods are not backed by the Subject
‘s respective internal sets. A new set is created and returned for each such method invocation. Modifications to these sets will not affect the Subject
‘s internal sets.
In order to iterate through a Set of private credentials, you need a javax.security.auth.PrivateCredentialPermission
to access each credential. See the PrivateCredentialPermission
API documentation for further information.
A Subject
may be associated with an
AccessControlContext
(see the doAs
and
doAsPrivileged
method descriptions in the following sections). The
following method returns the Subject
associated with the specified
AccessControlContext
, or null
if no
Subject
is associated with the specified
AccessControlContext
.
public static Subject getSubject(final AccessControlContext acc);
An AuthPermission
with target “getSubject” is required to call Subject.getSubject
.
The Subject
class also includes the following methods inherited from java.lang.Object
.
public boolean equals(Object o);
public String toString();
public int hashCode();
Окно регистрации java и функции входа в систему – русские блоги
Сначала поговорим о реализации функции в окне.
Перед реализацией функции вам необходимо знать несколько вещей:
1.Поскольку пароль и имя пользователя помещаются в txt, необходимы некоторые знания ввода-вывода.:
File file = new File (path); читать txt
file.exists () определяет, существует ли файл
file.createNewFile (); Создать новый файл
FileInputStream fis = new FileInputStream(file);
InputStream: это суперкласс всех байтовых входных потоков, и обычно используются его подклассы: FileInputStream и т. д., которые могут выводить байтовые потоки;
InputStreamReader isr = new InputStreamReader(fis,“utf-8”);
InputStreamReader: это мост между потоком байтов и потоком символов, может выводить поток байтов как поток символов и может указывать набор символов для потока байтов и может выводить отдельные символы ;
BufferedReader br = new BufferedReader(isr);
BufferedReader: обеспечивает общее чтение буферизованного текста, readLine считывает текстовую строку, считывает текст из потока ввода символов, буферизует каждый символ, тем самым обеспечивая эффективный символ, массив и строку Читать.
br.readLine () метод чтения
line.equals () метод сравнения строк
FileWriter fw = new FileWriter(“D:user.txt”,true);
// Создаем объект класса потока вывода символов и связываем его с существующим файлом. Если файл не существует, создайте его.
// Добавление true означает, что, когда fw снова запишет файл, он продолжит запись в конец файла и не будет его перезаписывать.
fw.write (str ” n”); Метод записи
2. Напишите общее представление о связанных функциях:
То есть определить, существует ли имя пользователя при регистрации, в противном случае запишите имя пользователя и пароль в текстовый файл и оцените, правильны ли имя пользователя и пароль при входе в систему, если да, то вход в систему успешен
Нужна функция регистрации UserRegister (), отвечающая за оценку успешности регистрации;
требует функции входа в систему UserLogin (), которая отвечает за оценку успешности входа;
Нужна функция IsUserExist (), чтобы определять, существует ли имя пользователя во время регистрации;
Нужна функция WriteInFile (), которая записывает имя пользователя и пароль в txt при регистрации;
Функция IsFileExist (), которая должна определить, существует ли текстовый файл в начале;
Затем конкретный код:
publicstaticbooleanIsUserExist(File file , String username)throws IOException {boolean flag =false;try{
FileInputStream fis =newFileInputStream(file);
InputStreamReader isr =newInputStreamReader(fis,"utf-8");
BufferedReader br =newBufferedReader(isr);
String line ="";while((line = br.readLine())!=null){if(line.equals("un" username)){
flag =true;break;}}
br.close();
isr.close();
fis.close();}catch(FileNotFoundException e){
e.printStackTrace();}return flag;}publicstaticvoidWriteInFile(File file,String str){try{
FileWriter fw =newFileWriter(file,true);
fw.write(str "n");
fw.close();}catch(IOException e){
e.printStackTrace();}}staticvoidUserRegister(File file,String username,
String passwd)throws IOException {if(!IsUserExist(file,"un" username)){WriteInFile(file,"un" username);WriteInFile(file,"pw" passwd);
System.out.println("Вы успешно зарегистрировались");}else{
System.out.println(«Имя пользователя уже зарегистрировано»);}}publicstaticvoidUserLogin(File file,String username,String passwd)throws IOException {
FileInputStream fis =newFileInputStream(file);
InputStreamReader isr =newInputStreamReader(fis,"utf-8");
BufferedReader br =newBufferedReader(isr);
String line ="";while((line = br.readLine())!=null){if(line.equals("un" username)){if(br.readLine().equals("pw" passwd)){
System.out.println("Авторизация успешна");break;}else{
System.out.println("неправильный пароль");break;}}}
br.close();
isr.close();
fis.close();}}
publicstaticvoidIsFileExist(File file){if(!file.exists()){try{
file.createNewFile();
System.out.println(1);}catch(IOException e){
e.printStackTrace();}}}
На основе предыдущих функций в сочетании с javafx:
Относительно просто использовать макет GridPane для отслеживания действий кнопок входа и регистрации.
Обобщите вещи в оконной химии:
1. Прежде всего, это должно быть использование сетки, например:
pane.add () добавляет элементы на панель
Есть инструкции по обработке местоположения добавленных нами вещей (хотя их все еще много, и я этого не понимаю)
pane.setAlignment( Pos.CENTER);
pane.setPadding(new Insets(16,17,18,19));
pane.setHgap(5.5);
pane.setVgap(9.5);
GridPane.setHalignment(login, HPos.RIGHT);
GridPane.setHalignment(register, HPos.RIGHT);
2. Есть эти два
TextField usertext = new TextField();
PasswordField pwtext = new PasswordField();
прочтите буквальное значение, одно – это видимое содержимое записи, а другое превратит вас в черную точку при написании, как мы обычно вводим пароль.
3. Наконец, использование стадии:
Scene scene = new Scene(pane);
primaryStage.setTitle («Интерфейс входа в систему»);
primaryStage.setScene(scene);
primaryStage.show();
Последнее предложение – открыть окно, эта часть немного похожа на макет Android
Далее идет общий код:
package src;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileWriter;import java.io.IOException;import java.io.InputStreamReader;import javafx.application.Application;import javafx.event.ActionEvent;import javafx.event.EventHandler;import javafx.geometry.HPos;import javafx.geometry.Insets;import javafx.geometry.Pos;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.scene.control.PasswordField;import javafx.scene.control.TextField;import javafx.scene.layout.GridPane;import javafx.stage.Stage;publicclassLoginandRegister_week13extendsApplication{
TextField usertext =newTextField();
PasswordField pwtext =newPasswordField();
String path ="D:\user.txt";
File file =newFile(path);@Overridepublicvoidstart(Stage primaryStage){
GridPane pane =newGridPane();
pane.setAlignment( Pos.CENTER);
pane.setPadding(newInsets(16,17,18,19));
pane.setHgap(5.5);
pane.setVgap(9.5);
pane.add(newLabel("username :"),0,0);
pane.add(usertext,1,0);
pane.add(newLabel("password :"),0,1);
pane.add(pwtext,1,1);
Button login =newButton("login");
Button register =newButton("register");
pane.add(login,1,2);
pane.add(register,1,3);
GridPane.setHalignment(login, HPos.RIGHT);
GridPane.setHalignment(register, HPos.RIGHT);
register.setOnAction(newEventHandler<ActionEvent>(){@Overridepublicvoidhandle(ActionEvent event){try{if(IsUserExist(file, usertext.getText())){
System.out.println("Пользователь уже существует");}else{try{UserRegister(file, usertext.getText(),pwtext.getText());}catch(IOException e){
e.printStackTrace();}}}catch(IOException e){
e.printStackTrace();}}});
login.setOnAction(newEventHandler<ActionEvent>(){@Overridepublicvoidhandle(ActionEvent event){try{if(IsUserExist(file, usertext.getText())){UserLogin(file, usertext.getText(), pwtext.getText());}else{
System.out.println(«Пользователь не зарегистрирован»);}}catch(IOException e){
e.printStackTrace();}}});
Scene scene =newScene(pane);
primaryStage.setTitle("интерфейс входа");
primaryStage.setScene(scene);
primaryStage.show();}publicstaticbooleanIsUserExist(File file , String username)throws IOException {boolean flag =false;try{
FileInputStream fis =newFileInputStream(file);
InputStreamReader isr =newInputStreamReader(fis,"utf-8");
BufferedReader br =newBufferedReader(isr);
String line ="";while((line = br.readLine())!=null){if(line.equals("un" username)){
flag =true;break;}}
br.close();
isr.close();
fis.close();}catch(FileNotFoundException e){
e.printStackTrace();}return flag;}publicstaticvoidWriteInFile(File file,String str){try{
FileWriter fw =newFileWriter(file,true);
fw.write(str "n");
fw.close();}catch(IOException e){
e.printStackTrace();}}staticvoidUserRegister(File file,String username,
String passwd)throws IOException {if(!IsUserExist(file,"un" username)){WriteInFile(file,"un" username);WriteInFile(file,"pw" passwd);
System.out.println("Вы успешно зарегистрировались");}else{
System.out.println(«Имя пользователя уже зарегистрировано»);}}publicstaticvoidUserLogin(File file,String username,String passwd)throws IOException {
FileInputStream fis =newFileInputStream(file);
InputStreamReader isr =newInputStreamReader(fis,"utf-8");
BufferedReader br =newBufferedReader(isr);
String line ="";while((line = br.readLine())!=null){if(line.equals("un" username)){if(br.readLine().equals("pw" passwd)){
System.out.println("Авторизация успешна");break;}else{
System.out.println("неправильный пароль");break;}}}
br.close();
isr.close();
fis.close();}}
Ошибки в реализации функции раньше: (пока не понял)
1. Определите, существует ли имя пользователя, а оператор if не может существовать.
2. fw.close () должен существовать
Опыт:
Сначала мне показалось, что это гигантская задача, но позже она стала яснее после разделения небольших функций.
Я не знаю, не знаю ли я или не знаком с некоторыми конкретными функциями реализации, я действительно могу полагаться только на информацию в Интернете.
При написании этого кода я сначала нашел пример кода в Интернете. Прочитав его полностью,
Затем напишите свои собственные идеи и постепенно улучшайте код. Если вы не понимаете его, прочтите еще раз и, наконец,
был реализован, и я чувствую, что многого удалось добиться.
https://blog.csdn.net/baidu_25506845/article/details/78606648
Это пример, о котором я говорю, спасибо, брат!
Применение jaas в web-приложениях на glassfish v2
На этот раз хочется написать про применение JAAS (Java Authentification and Authorization Service) для веб-приложений. Для начала рассмотрим простой контроль доступа к веб-ресурсам и авторизацию. Я попытаюсь раскрыть основную идею, а также дам подсказку по способу развёртывания (позже из текста станет понятно в чём проблема).
Прежде чем переходить к практике, следует осветить несколько основных понятий и общую схему контроля доступа к веб-ресурсам.
Когда веб-контейнер «тречит» сессии всех подключающихся к нему машин, то с каждой такой сессией могут быть ассоциированы роли, а также user principal. Каждая роль связана с возможностью или невозможностью доступа к некоторому набору веб-ресурсов, а также осуществления некоторых действий (privileged action).
Изначально сессия пользователя не связана ни с каким user principal’ом и ни с одной ролью. Это означает, что если на доступ к некоторому набору веб-ресурсов наложено ограничениче (constraint), то он не будет иметь доступ к этим ресурсам.
Для того, чтобы пользовательская сессия стала ассоциироваться с каким-то набором ролей, пользователь должен пройти авторизацию.
Авторизация осущестляется с помощью JAAS. Во время авторизации пользователь посылает свои креденциалы (credentials, в простом случае это могут быть логин: пароль или пользовательский сертификат). На сервере с процессом контроля связаны две сущности, а именно Realm и Login Module. Login Module осуществляет проверку связи пользователя с каким-то набором групп пользователей (не путать с ролями). Кроме того, авторизация может быть пройдена успешно, однако, пользователь может быть не связан ни с одной из групп.
Веб-приложение может определять соотношение между группами и ролями. Приложение задаёт это соответствие с помощью sun-web.xml.
Login Module и Realm не являются частью веб-приложения, а являются разделяемыми ресурсами сервера приложений, так что они должны быть в classpath сервера и должны быть соответствующим образом зарегистрировны в сервере (в login.conf и domain.xml). Приложение выбирает realm по имени через web.xml.
Приложение определяет набор ограничений на доступ к своим ресурсам через web.xml и указывает: какие веб-реурсы (по шаблону URL (url pattern)) могут быть доступны для каких видов запросов (get, post, head, etc) и для каких ролей. Кроме того, приложение определяет способ авторизации. Например, можно указать страницу логина или задать использование стандартной HTTP-авторизции (браузер показывает окно авторизации).
За выполнением ограничений средит веб-контейнер, так что приложению не требуется выполнять дополнительные проверки. Поскольку ограничения накладываются на шаблон URL, то не имеет значения что подпадает под этот шаблон, будь то сервлет, JSP или же даже ничего (404). Даже если ничего (404), а ограничение требует наличия у пользователя некоторой роли, то ему придётся пройти авторизацию для того чтобы увидеть, что там ничего нет.
Каждый realm может быть ассоциирован с каким-нибудь Login Module. Для этого, в свойствах (properties) для него указывает специальная пара «jaas-context» -> «login-module-name». Иногда Realm и Login Module работают только в паре. Например, если мы захотим написать свой Login Module и Realm, то вполне возможно захочется чтобы наш Realm мог работать только с нашим же Login Module и ни с каким другим.
Для примера создадим простую пару для авторизации по паролю.
Начнём с написания Realm’а:
public class TestRealm extends AppservRealm { @Override public String getAuthType() { return "magic"; } @Override public Enumeration getGroupNames( String string ) throws InvalidOperationException, NoSuchUserException { return Collections.enumeration( Arrays.asList( "users", "guests" ) ); } @Override protected void init( Properties props ) throws BadRealmException, NoSuchRealmException { super.init( props ); System.err.println( "Realm:: Hello!!" ); if( props.containsKey( JAAS_CONTEXT_PARAM ) ) setProperty( JAAS_CONTEXT_PARAM, props.getProperty( JAAS_CONTEXT_PARAM ) ); } @Override public AuthenticationHandler getAuthenticationHandler() { return null; } }
Метод getGroupNames возвращает enum с возможными группами. Метод init вызывается при инициализации (по факту — при старте домена). В нём мы можем выполнить требуемую инициализацию (например, достать необходимые паретры из props, а затем куда-то сохранить их для последующего использования). Очень важно не забыть передать свойство JAAS_CONTEXT_PARAM («jaas-context»), иначе реальм не будет работать.
Далее, опишем наш Login Module. Будем использовать авторизацию по паролю.
public class TestLoginModule extends AppservPasswordLoginModule { @Override protected void authenticateUser() throws LoginException { if( _username == null || _password == null ) throw new LoginException( "Username of password is null" ); if( "user".equals( _username ) && "user-pass".equals( _password ) ) commitUserAuthentication( new String[]{ "users" } ); else if ( "guest".equals( _username ) && "guest".equals( _password ) ) commitUserAuthentication( new String[]{ "guests" } ); else throw new LoginException( "bad login/password" ); } }
В этом примере мы просто «забили» варианты паролей. Вместо этого можно было бы «спросить» у БД или у LDAP-сервера, а может, даже, и то, и другое.
После сборки получим простой jar с двумя классами (для успешной сборки в classpath надо иметь javaee.jar, appserv-rt.jar и appserv-ext.jar и директории glassfish-v2/lib).
После сборки, наш jar можно положить, например, в директорию lib домена.
Далее, Realm и Login Module следует зарегистрировать в домене.
Для начала, надо позаботиться о том, чтобы наш .jar был в classpath сервера. Вероятно, существует неколько способов это сделать надлежащим образом, однако, в документации по теме описывается один конкретный, вот его-то мы и будем использовать.
В файле domain.xml среди всего прочего описываются параметры jvm. Там можно найти тэг java-config. У него есть аттрибут classpath-suffix. Обычно он пуст. Вот туда-то нам и надо «вписаться».
Например:
... <java-config classpath-suffix="/home/cy6ergn0m/.domains/domain1/lib/MyTestRealm.jar" ... ....
После этого, надо добавить наш Login Module. Это делается в текстовом файле login.conf в директории домена (domain_dir/conf/login.conf). Можно просто дописать в конец, например, так:
testRealmLM { cy6ergn0m.auth.TestLoginModule required; };
Когда метод логина описан, можно зарегистрировать наш именованный Realm. Его можно добавить в domain.xml или через web admin console. Мы сделаем это через domain.xml.
Среди тэгов верхнего уровня можно найти тэг security-service. Обычно в нём уже есть несколько тэгов auth-realm. Мы добавим туда же и свой. Однако, важно, чтобы наш тэг был после auth-realm’ов, которые уже есть, т.к. схема domain.xml требует, чтобы auth-realm’ы были в начале.
<auth-realm classname="cy6ergn0m.auth.TestRealm" name="testRealm"> <property name="jaas-context" value="testRealmLM"/> <property name="auth-type" value="magic"/> </auth-realm>
Среди параметров (properties) мы можем передать и другие. Эти свойства потом попадают в метод init нашего Realm’а. Сюда мы можем положить, например, адреспорт сервера или ещё какие-то настройки.
Следует также заметить, что можно создать несколько реалмов, но с разными параметрами. Login Module в свою очередь всегда может узнать, который из реалмов его использует с помощью protected-поля _currentRealm. Также, таким способов он может получить у реалма какие-то специфичные настройки (хост, порт, и т.п.).
Теперь, следует перезапутить домен. При старте домена в логе мы сможем увидеть нашу запись: «Realm:: Hello!!». Если этого не произошло, то скорее всего вы что-то сделали неверно.
Итак, наш модуль авторизации готов к употреблению. Попробуем воспользоваться им.
Для этого напишем крошечное web приложение, которое будет использовать наш модуль.
Создадим несколько web-страниц, для начала сделаем три: index.html, secret.html и for-guests.html.
Сделаем ссылки с index.html на остальные две.
После этого, создадим ограничения на доступ к нашим «страшно секретным» страницам. Для этого придётся редактировать файлы web.xml и sun-web.xml.
Наш модуль авторизации умеет авторизовать пользователей двух групп: «user» и «guest. Давайте создадим две роли соответствующие этим группам.
Откроем файл sun-web.xml и добавим эти соотношения. Для этого, добавим в него две секции <security-role-mapping>:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd"> <sun-web-app error-url=""> <context-root>/TestRealmClient</context-root> <security-role-mapping> <role-name>user</role-name> <group-name>users</group-name> </security-role-mapping> <security-role-mapping> <role-name>guest</role-name> <group-name>guests</group-name> </security-role-mapping> <class-loader delegate="true"/> <jsp-config> <property name="keepgenerated" value="true"> <description>Keep a copy of the generated servlet class' java code.</description> </property> </jsp-config> </sun-web-app>
Теперь опишем те роли, которые хотим использовать в web.xml:
<security-role> <description/> <role-name>user</role-name> </security-role> <security-role> <description/> <role-name>guest</role-name> </security-role>
Теперь можно создавать ограничение (constraint). Для этого исправим web.xml и добавим в него ограничения:
<security-constraint> <display-name>Constraint1</display-name> <web-resource-collection> <web-resource-name>secrets</web-resource-name> <description/> <url-pattern>/secret*</url-pattern> </web-resource-collection> <auth-constraint> <description/> <role-name>user</role-name> </auth-constraint> </security-constraint> <security-constraint> <display-name>Constraint2</display-name> <web-resource-collection> <web-resource-name>guests</web-resource-name> <description/> <url-pattern>/for-guests*</url-pattern> </web-resource-collection> <auth-constraint> <description/> <role-name>guest</role-name> <role-name>user</role-name> </auth-constraint> </security-constraint>
Таким образом мы определили, что доступ к страницам /secret* будет только для пользователей с ролью user, а адреса /for-guests* для пользователей с ролями user и guest. Это означает, что пользователь с ролью user может посещать все страницы, пользователь с ролью guest только index.html и for-guests.html, а „никто“ — только index.html.
Теперь мы должны указать realm и способ авторизации. Для начала выберем простой способ (HTTP-авторизация) — BASIC. Для этого в web.xml укажем:
<login-config> <auth-method>BASIC</auth-method> <realm-name>testRealm</realm-name> </login-config>
Таким образом, в конце у нас получится следующий web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <security-constraint> <display-name>Constraint1</display-name> <web-resource-collection> <web-resource-name>secrets</web-resource-name> <description/> <url-pattern>/secret*</url-pattern> </web-resource-collection> <auth-constraint> <description/> <role-name>user</role-name> </auth-constraint> </security-constraint> <security-constraint> <display-name>Constraint2</display-name> <web-resource-collection> <web-resource-name>guests</web-resource-name> <description/> <url-pattern>/for-guests*</url-pattern> </web-resource-collection> <auth-constraint> <description/> <role-name>guest</role-name> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>testRealm</realm-name> </login-config> <security-role> <description/> <role-name>user</role-name> </security-role> <security-role> <description/> <role-name>guest</role-name> </security-role> </web-app>
В редакторе web.xml в netbeans это будет выглядеть следующим образом:
Готово. Теперь приложение можно запустить и проверить. При попытке перейти по одной из ссылок возникнет окно авторизации.
Давайте усложним задачу. Допустим, мы хотим сами сделать свою собственную страницу логина.
Достичь этого мы можем двумя способами. Расмотрим оба.
Для начала мы должны изменить способ авторизации в web.xml. Ранее вы указывать метод авторизции BASIC. Теперь мы выберем логин-форму.
<login-config> <auth-method>FORM</auth-method> <realm-name>testRealm</realm-name> <form-login-config> <form-login-page>/login.jsp</form-login-page> <form-error-page>/login.jsp?fail</form-error-page> </form-login-config> </login-config>
Теперь создадим форму логина login.jsp согласно первому способу, более простому:
<%@page contentType="text/html" pageEncoding="windows-1251"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <title>JSP Page</title> </head> <body> <h1>Hello World!</h1> <h2>Web container's login method</h2> <form action="j_security_check" method="post"> <input type="text" name="j_username" /> <input type="password" name="j_password" /> <input type="submit" /> </form> </body> </html>
Готово. Теперь, когда неавторизованный пользователь будет пытаться пройти на „защищённые“ страницы, веб-контейнер будет „редиректить“ его на нашу форму логина. После авторизции пользователь получит необходимые роли и сможет посещать „защищённые“ страницы.
В некоторых случаях нам нужем больший контроль за процессом авторизации и какой-то неконтролируемый нами j_security_check нас не устраивает. В таком случае, мы всегда можем воспользоваться вторым способом. Мы можем вручную выполнить логин. Поскольку Glassfish использует нестандартный логин-модуль (не случайно мы реализовывали AppservPasswordLoginModule вместо стандартного JAAS интерфейса), то мы не можем воспользоваться обычным путём авторизации через LoginContext, а должны использовать нестандартное API сервера (точнее можем, но это сложнее и всё равно непереносимо).
Мы создадим LoginServlet, который будет использовать ProgrammaticLogin из API Glassfish’а [7]. Следует обратить внимание, что для того, чтобы класс бы виден, необходимо добавить файлы javaee.jar, appserv-rt.jar и appserv-ext.jar в classpath при сборке (и в вашей IDE, что возможно одно и то же).
public class LoginServlet extends HttpServlet { @Override protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { response.sendError( HttpServletResponse.SC_FORBIDDEN ); } @Override protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { ProgrammaticLogin pl = new ProgrammaticLogin(); try { Boolean rc = pl.login( request.getParameter( "name"), request.getParameter( "pass"), "testRealm", request, response, true ); if( rc != null && rc.booleanValue() ) { response.sendRedirect( "index.jsp" ); return; } } catch( Exception ex ) { Logger.getLogger( LoginServlet.class.getName() ).log( Level.SEVERE, null, ex ); } response.sendRedirect( "login.jsp?fail" ); } @Override public String getServletInfo() { return "Login servlet"; } }
А страница login.jsp изменим соответственно так, чтобы параметры формы направляли в LoginSerlvet
<%@page contentType="text/html" pageEncoding="windows-1251"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <title>JSP Page</title> </head> <body> <h1>Hello World!</h1> <h2>Web container's login method</h2> <form action="login" method="post"> <input type="text" name="name" /> <input type="password" name="pass" /> <input type="submit" /> </form> </body> </html>
Таким образом, получим сервлет логина, который можем использовать любым удобным для нас способом, например, мы можем ещё сделать „каптчу“, или проверять логин аяксом и многое другое.
В заключение хочется осветить ещё один момент. Дело в том, что регистрация Realm и Login Module не слишком простая, то хотелось бы иметь возможность автоматически регистрировать их в произвольном домене.
Для реализации задумки можно сделать свою собственную „таску“ (Custom Task) для Ant. Её можно написать на Java и выполнить все необходимые действия.
По служебному долгу пришлось написать подобную вещь, но, по понятным причинам не могу привести тут этот код. Однако, дам несколько намёков о её содержании. К файлу login.conf можно легко дописать пару строчек через FileWriter, созданный с парметром append=true. С манипуляциями над domain.xml сложнее, однако, это ведь XML, к тому же обозримого размера… стало быть, можно с помощью DocumentBuilder’а прочеть весь XML в DOM-дерево, потом внести необходимые поправки в дерево, добавить classpath-suffix и auth-realm, а потом серилизовать DOM-дерево в domain.xml обратно. У меня получилось уложиться в 200 строк вместе с проверками входных параметов. Такую таску можно включить в ant-скрипты создания всего домена (если домен создаётся ant’ом).
Несмотря на то, что тестировал я всё в glassfish v2, однако, судя по разного рода источникам, можно надеяться, что для v3 это также актуально, хотя возможно потребует корректировок.
На этом непростая статья подошла к концу. Конечно, многое осталось за кадром (privileged actions, user principals), но я надеюсь, что мне удалось пролить свет на эту непростую нишу веб-разработки.
Успехов.
CG.
Список литературы ака ссылки:
1. Authentication Using Custom Realms in Sun Java System Application Server
developers.sun.com/appserver/reference/techart/as8_authentication
2. Using JAAS with Tomcat
www.kopz.org/public/documents/tomcat/jaasintomcat.html
3. JAAS Tomcat Login Module
www.owasp.org/index.php/JAAS_Tomcat_Login_Module
4. JavaTM Authentication and Authorization Service (JAAS). LoginModule Developer’s Guide.
java.sun.com/javase/6/docs/technotes/guides/security/jaas/JAASLMDevGuide.html
5. Sun GlassFish Enterprise Server v3 Application Development Guide
Chapter 5 Securing Applications
docs.sun.com/app/docs/doc/820-7695/beabg?l=ru&q=glassfish JAAS&a=view
6. Ветка на форуме про веб-логин через j_security_check
www.sql.ru/Forum/actualthread.aspx?tid=624508
7. Javadoc Class ProgrammaticLogin
glassfish.dev.java.net/nonav/docs/v3/api
8. Sun GlassFish Enterprise Server v3 Application Development Guide
Programmatic Login
docs.sun.com/app/docs/doc/820-7695/beacm?l=ru&a=view
9. Implement JAAS based Authentication and Authorization for ADF Faces applications on OC4J 10.1.3
technology.amis.nl/blog/1426/implement-jaas-based-authentication-and-authorization-for-adf-faces-applications-on-oc4j-1013
10. Securing a Web Application on Glassfish using JAAS — Part 1 and 2
www.developinjava.com/features/47-enterprise-java/105-securing-a-web-application-on-glassfish-using-jaas.html
www.developinjava.com/features/47-enterprise-java/106-securing-a-web-application-on-glassfish-using-jaas-pt-2.html
11. Glassfish javadoc: Class AppservRealm
glassfish.java.net/nonav/javaee5/api/com/sun/appserv/security/AppservRealm.html
Шаг 1. типы реализации
У нас есть несколько вариантов безопасности, которую мы можем использовать. Начнем с того, что объяснит нам всю суть.
Ручная реализация
Суть состоит в том, что мы берем на себя ответственность за реализацию безопасности в нашем приложении, сами придумываем умные алгоритмы защиты, шифрования, на каждой страничке делаем проверку на правомерность пользователя выполнять действия.
И если что-то пойдет не так, мы допустим ошибку в своем алгоритме, то приложение стает под угрозой перед злоумышленниками.
Реализация с помощью сервера приложений
Естественно безопасность разрабатывается очень часто и есть уже конкретные шаблонны, как именно ее организовывать.
Чтобы уйти от написания безопасности вручную, уменьшить вероятность взлома системы и ускорить разработку каждый может воспользоваться реализацией безопасности, которую предлагает нам сервер приложений. Это хорошо продуманная и гибкая система.
Шаг 3. настройка безопасности
Думаю для начала этих знаний будет достаточно, с остальной частью мы уже познакомимся на практике. Постановка задачи Пусть у нас будет приложение, которое имеет несколько страниц, которые будут доступны конкретной группе пользователей, таких как менеджеры или администраторы.
Мы создали основную структуру. Пока что нету ничего кроме статического html. Сейчас все ссылки доступны с приложения и можно дойти к любому файлу. Есть специально создано 2 ресурса: public, secured. Первым делом, ограничим доступ ко всему что находиться внутри secured folder.
Пример файла web.xml:
Шаг 4. аутентификация
Добавим теперь нашему приложению возможность аутентификации, которая есть у нас по умолчанию. Методы аутентификации:
1. BASIC – Здесь используется стандартная форма ввода данных для аутентификации.
При доступе к закрытым ресурсам вы увидите окно, которое попросит вас ввести свои данные.
2.FORM – Здесь используем свою html форму. Делаем настройки в web.xml:
login-config – конфигурация аутентификации.
auth-method – каким методом проводить аутентификацию. Если выбираем FORM, то нужно ввести дополнительную информацию:
form-login-config – дополнительная информация при использовании своей формы
Шаг 5. настройка сервера приложений
Теперь, осталось настроить наш сервер приложений и связать его с нашим приложением. Для связки, в зависимости от сервера, в приложении используется специальный файл, который имеет название: *-web.xml.
JBoss, WildFly: jboss-web.xml
GlassFish: sun-web.xml
Создаем нужный нам файл в директории WEB-INF.
В данном случае мы будем использовать properties файлы у нас на сервере, что будут хранить данные о пользователях.
Пример файла jboss-web.xml:
Шаг 6. использование безопасности в сервлетах
Здесь очень кратко покажу базовые настройки для сервлетов. Для этого, создадим 2 сервлета в нашем проекте.
SecuredServlet1 – если посмотреть на адресс, защищен с помощью настроек в web.xml
Conclusion
In this article, we have showcased how to implement JAAS by exploring the principal classes and interfaces and showing how to configure them. Especially, we have implemented a service provider LoginModule.
As usual, the code in this article is available over on GitHub.
1. initialize()
The LoginModule is first loaded and then initialized with a Subject and a CallbackHandler. Additionally, LoginModules can use a Map for sharing data among themselves, and another Map for storing private configuration data:
public void initialize(
Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
}